《iPhone 3D 编程》第二章:数学与抽象

第二章:数学与抽象

 

计算机图形领域比计算机其它领域对数学的要求都高,如果你想成为一个合格的OpenGL程序员,那么你得撑握线性代数,并能抽象一些内容。

 

在本章,我将解释这些抽象内容与回忆线性代数的内容。其中OpenGL涉及到的概念都会得到讲解,于是在HelloArrow示例代码中的神密面纱将一层一层解开。

 

在本章结束时,我们会运用这些数学知识将HelloArrow这个示例转化为到3D空间中去,完成一个叫”HelloCone”的示例。

 

集装线的抽象概念

你们可以 把包括OpenGL ES在内的任何图形API都看作是集装线的工程流程,将各种原始材料如纹理,顶点做为输入,*终组装成五彩缤纷的图形。

 

这些传入OpenGL的数据是我们学习OpenGL首先要学的内容,在本章我们重点学习顶点。在图2.1中,用抽象的视角展示了顶点逐渐演变成像素的过程。首先是一系列的顶点变换,接着这些顶点被组装成图元,*后将这些图元光栅化到像素。

 

图2.1 OpenGL集装线

%title插图%num

注意

OpenGL ES 1.1与2.0都能抽象出集装线的概念,但ES 2.0更为明显。图2.1中,*左边的小精灵接手处理vertex shader,*右边的小精灵处理完后交给fragment shader。

 

在本章我们重点介绍集装流程中的变换,但是首先我们概述一下装配图元的流程,因为它很容易理解。

装配顶点为图元

在三维空间中,一个物体的形将可以用几何图形表示。在OpenGL中,这些几何图形是由基础图元,这些基础图元包括三角形,点,线。其础元是由一些顶点通过不同的拓扑规则构建起来的。在OpenGLES中,共有7种拓扑规则,参看图2.2“图形拓扑规则”。

图2.2 “图形拓扑规则”

%title插图%num

 

 

在*章Hello Arrow的代码中,有这样一行代码利用OpenGL绘制一个三角形到缓冲区:

glDrawArrays(GL_TRIANGLES, 0, vertexCount);

 

*个参数指明OpenGL绘制的时候拓扑规则为:GL_TRIANGLES,采用这种规则后OpenGL组装基础图形的时候,首先取顶点缓冲区中的前三个顶点出来组成*个三角形,接着到后面三个顶点组成第二个三角形,以此类推。

 

大多数情况下,同于顶点都挨着的,所以在顶点组数中会有重复出现的。为了解决这个问题,GL_TRIANGLE_STRIP规则出来了。这样一来,就可以用更小的顶点数组绘制出数量相同的三角形,看表2.1会明了许多,其中v表示顶点数,p表示图元数。这样说吧,如果绘制三个三解形,用GL_TRIANGLES规则,我们需要9个顶点数据(3*p),如果用GL_TRIANGLE_STRIP规则,我们则只需要5个顶点数据(p+2)。

 

表2.1 图元相关计数

拓扑规则

图元数

顶点数

GL_POINTS v p
GL_LINES v/2 2p
GL_LINE_LOOP v p
GL_LINE_STRIP v-1 p+1
GL_TRIANGLES v/3 3p
GL_TRIANGLE_STRIP v-2 p+2
GL_TRIANGLE_FAN v-1 p+1

 

GL_RTINGLE_FAN这个规则得说一下, 花多边形,圆或锥体的时候这个规则很好用。*个顶点表示顶点,其它的表示底点。很多种情况下都是用GL_TRINGLE_STRIP,但是在用FAN的时候如果用成了STRIP,那么这个三角形将退化(0区域三角形)。

 

图2.3 两个三角形组成的四方形

%title插图%num

        图2.3中用两个三角形绘制了一个方形。(顺便说一下,OpenGL有一种规则GL_QUADS是用来直接绘制方形的,但是OpenGL ES不支持这种规则。)下面的代码分别用三种拓扑规则绘制同一个方形三次。

 

const int stride = 2 * sizeof(float);

 

float triangles[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 1}, {1, 0}, {0, 0} };

glVertexPointer(2, GL_FLOAT, stride, triangles);

glDrawArrays(GL_TRIANGLES, 0, sizeof(triangles) / stride);

 

float triangleStrip[][2] = { {0, 1}, {0, 0}, {1, 1}, {1, 0} };

glVertexPointer(2, GL_FLOAT, stride, triangleStrip);

glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(triangleStrip) / stride);

 

float triangleFan[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 0} };

glVertexPointer(2, GL_FLOAT, stride, triangleFan);

glDrawArrays(GL_TRIANGLE_FAN, 0, sizeof(triangleFan) / stride);

 

在OpenGL ES中图元并不是只有三角形,GL_POINTS可以用来绘制点。点的大小是可以自定义的, 如果太大看起来就像方形。这样一来,就可以将小的位图与这样的点关联起来,构成所谓的点精灵。在第七章,精灵与字符中会讲到。

 

OpenGL中关于线的图元拓扑规则有三个,分别是:separatelines, strips与loops。在strips与loops规则中,每一条件的结束点是下一条线的顶点,而loops更特别,*条线的开始点是*后一条件的结始点。如果你绘制图2.3中方形的边框,下面的代码分别用三种规则实现了。

const int stride = 2 * sizeof(float);

 

float lines[][2] = { {0, 0}, {0, 1},

{0, 1}, {1, 1},

{1, 1}, {1, 0},

{1, 0}, {0, 0} };

glVertexPointer(2, GL_FLOAT, stride, lines);

glDrawArrays(GL_LINES, 0, sizeof(lines) / stride);

 

float lineStrip[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0} };

glVertexPointer(2, GL_FLOAT, stride, lineStrip);

glDrawArrays(GL_LINE_STRIP, 0, sizeof(lineStrip) / stride);

 

float lineLoop[][2] = { {0, 0}, {0, 1}, {1, 1}, {1, 0} };

glVertexPointer(2, GL_FLOAT, stride, lineLoop);

glDrawArrays(GL_LINE_LOOP, 0, sizeof(lineLoop) / stride);

 

涉及顶点的属性

 

现在来看看OpenGL中集装线的输入数据。在OpenGL的世界里,每一个顶点至少得有一个属性,其中位置是*为重要的。表2.2罗列了OpenGL ES 1.1的顶点属性。

 

表2.2 OpenGL ES中的顶点属性

Attribute

OpenGL Enumerant

OpenGL Function Call

Dimensionality

Types

Position GL_VERTEX_ARRAY

 

glVertexPointer

 

2, 3, 4

 

byte, short, fixed, float

 

Normal GL_NORMAL_ARRAY

 

glNormalPointer

 

3

 

byte, short, fixed, float

 

Color GL_COLOR_ARRAY

 

glColorPointer

 

4 ubyte, fixed, float

 

Point Size GL_POINT_SIZE_ARRAY_OES

 

glPointSizePointerOES

 

1 fixed, float

 

Texture Coordinate GL_TEXTURE_COORD_ARRAY

 

glTexCoordPointer

 

2,3,4 byte, short, fixed, float

 

Generic Attribute(ES 2.0) N/A

 

glVertexAttribPointer

 

1,2,3,4 byte, ubyte, short, ushort, fixed, float

 

 

OpenGL ES 2.0只有*后一行,它需要你自定义属性。回忆一下HelloArrow中不同rendering engines开启属性的方法:

 

//ES 1.1

glEnableClientState(GL_VERTEX_ARRAY);

glEnableClientState(GL_COLOR_ARRAY);

 

//ES 2.0

glEnableVertexAttribArray(positionSlot);

glEnableVertexAttribArray(colorSlot);

 

在ES 1.1中,是用内置常量来开启顶点属性的,而ES 2.0中则是用从shader中导出的常量来开始(positionSlot与colorSlot)。接着向OpenGL指明要开启顶点属性的类型与维度:

 

 

    // OpenGL ES 1.1

glVertexPointer(2, GL_FLOAT, … );

glColorPointer(4, GL_FLOAT, … );

 

// OpenGL ES 2.0

glVertexAttribPointer(positionSlot, 2, GL_FLOAT, …);

glVertexAttribPointer(colorSlot, 4, GL_FLOAT, …);

 

顶点数据的数据类型可能是表2.3中的一个。如果是ES 2.0可以使用其中任意一个,而ES 1.1则有限制, 具体要看是什么属性(参看表2.2*右列)。

 

表2.3 顶点属性数据类型

OpenGL Type

OpenGL Enumerant

Typedef Of

Length in Bits

GLbyte GL_BYTE signed char 8
GLubyte GL_UNSIGNED_BYTE unsigned char 8
GLshort GL_SHORT short 16
GLushort GL_UNSIGNED_SHORT unsigned short 16
GLfixed GL_FIXED int 32
GLfloat GL_FLOAT float 32

 

OpenGL ES 1.1中,位置属性有点特殊,因为它是必须的顶点属性。它可以是二维,三维或是四维,但是在OpenGL内部总是把它们转化为四维浮点型进行处理。

 

        四维空间?这可与那些物理学家所说的不一样, 它并不涉及时间与物理,只是一种方法,它可以将所以变换都用矩阵相乘的方式表达。这里的四维坐标就是我们所谓的齐次坐标。当把三维坐标转化为齐次坐标的时候,往往第四个元素是为1(通宵用w表示),为0的情况表示点无限远, 这种情况非常少。(在OpenGL中在设置灯光位置的时候w=0,第四章中会看到。),为负的情况是没有实际意义的。

 

 

齐次坐标

        齐次坐标是在Möbius于1827年8月发表Der barycentrische Calcul中诞生的。随便说说Möbius发明的barycentrische坐标系,它用于iPhone图形芯片中计算三角形插值颜色。这个术语源于古老的词汇“barycentre”,表示中心的意思。如果你将一个三角的三个角放上不同的权重,那么你就可以通过barycentric坐标系计算平衡点。关于它的推导不在本书讨论的范围,如果你有兴趣可以自行研究。

 

再次回到OpenGL集装线流程,其中所有的点都变为4维,那么它们可能变成2维的点吗?明确的告诉你,会的!特别是苹果发布了触摸屏的iPhone。我们将在下一节介绍顶点是如何变化为2维点,现在我们首先关心如何拆除第四个变量w的,方程式如下:

方程式 2.1 透视变换

 

这种除以w的计算就叫着透视变换。z也进行同样的处理,紧接着的深度与真实性,你会看到更深入分析。

 

顶点的生命周期

图2.4, “顶点前期流程。上一排是概念,下一排是OpenGL的视图”与 图2.5,“光珊化前顶点的*后三个流程”描绘了顶点从三维变到二维的过程。在OpenGL的集装线中,它们叫着变换与灯光,或用T&L表示。我们将在第四章,深度与真实性中介绍灯光,现在重点是介绍变换。

 

每一次变换,顶点就有新的位置。*原传入的顶点是位于对象空间,即叫着对象坐标系。在对象空间中,原点就是对象的中心点,有时候我们把对象空间也叫着模型空间。

 

通过模型-视图矩阵,对象坐标就被转化为眼坐标空间。在眼坐标空间中,原点是照像机的位置。

 

接着,通过投影矩阵顶点变转化到裁剪空间。由于OpenGL将位于视图平截面外的顶点会切除掉,所以形像的叫着裁剪空间。在这儿就是w表演的时候了,如果x或y的值大于+w或小于-w,这些点将会被裁剪掉。

 

图2.4 顶点的先期流程。上一排是概念,下一排是OpenGL的视图

 %title插图%num

 

在ES 1.1中,图2.4中的流程是固定的,每一个顶点都必须经过这些流程。在ES2.0中,这取决于你,在进入裁剪空间前,你可以进行任何的变换。但常常你也是进行与此相同的变换而已。

 

裁剪过后,就进入到透视变换了。我们会把坐标标准化到[-1, +1],术语叫着设备坐标标准化。图2.5描述了其变换过程。与图2.4不同的是,这些流程在ES1.1与ES2.0中都是一样的。

 

图2.5光珊化前顶点的*后三个流程

 %title插图%num

光珊化前前*后一步是视口变换,它需要一些该应中当中设定的值。你可以还认得在GLViw.mm中有这样一行代码:

glViewport(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));

 

glViewport的四个参数分别是:left,bottom,with,height。对于iPhone,往往让width与height的值为320与480,但是为了兼容以后的苹果设备(与其它平台)与避免硬编码,我们需要在运行时获取正确的长度与高度,正如我们在HelloArrow中所写的代码。

 

glViewport控制x与y变换到窗口空间(也可叫着移动设备,非全屏的情况少之又少)。控制z变换到窗口空间是由另一个方法实现的:

glDepthRangef(near, far);

 

实际开发中,这个方法很少用, 它的默认值是near为0,far为1,我们用它的默认值即可。

 

现在,你明白顶点位置变化的基本流程了吧,但是我们还没有介绍颜色。当灯点禁止(默认禁止)的时候,颜色是不经过变换而直接传递。当开启的时候,颜色就与变换有密切关系。我们将在第四章,深度与真实性介绍。

 

摄影抽象

线装线的抽象让我们明白了OpenGL的后台工作原理,但是对于理解一个3D应用的工作流程,摄影抽象更加有用。当我太太精心准备了印度晚餐,他就会要求我拍摄一些相片用于它的私人博客。我常常会做下面的流程来完成太太的要求:

1.    放置各种餐盘。

2.    放置灯光。

3.    放置相机。

4.    将相机对准食物。

5.    设整焦距。

6.    控快门拍相。

 

It turns out that each of these actions haveanalogues in OpenGL, although they typically occur in a different order.Setting aside the issue of lighting (which we’ll address in a future chapter),an OpenGL program performs the following actions:

 

你可能已发现,每一步都与OpenGL中有相类之处,尽管有的顺序不同。先把灯光部份放一边(这部份内容在后面章节),OpenGL的的步骤如下:

1.    调整相机的视角, 投影矩阵起作用。

2.    放置相机位置并设置朝向,视图矩阵起作用

3.    对于*个对象

a.    缩放,旋转,移动,模型矩阵起作用。

b.    渲染对象。

 

模型矩阵与视图矩阵的合体叫着模型-视图矩阵。在OpenGLES 1.1中,所有的顶点都先经过模型-视图矩阵作用,然后再由投影矩阵作用。而OpenGL ES 2.0中, 你可以任意变换, 但是常常也是按照模形-视图/投影的过程变换,至少得差不多。

在后面我们会详细介绍三种变换,现在来学一些预备识知。无论如何用,OpenGL有一个通用的方法来处理所有的变换。在ES1.1中,当前变换可以用矩阵来表达,如下:

     float projection[16] = { … };

float modelview[16] = { … };

 

glMatrixMode(GL_PROJECTION);

glLoadMatrixf(projection);

 

glMatrixMode(GL_MODELVIEW);

glLoadMatrixf(modelview);

 

在ES2.0中,并没有模形-视图矩阵,也没有glMatrixMode与glLoadMatrixf这样的方法。取而代之的是shaders中的uniform变量。在后面我们会学到,uniforms是一种shader中用到的类型,我们可以简单的把它理解为shader不能改变的常量。加载方式如下:

    float projection[16] = { … };

float modelview[16] = { … };

 

GLint projectionUniform = glGetUniformLocation(program, “Projection”);

glUniformMatrix4fv(projectionUniform, 1, 0, projection);

 

GLint modelviewUniform = glGetUniformLocation(program, “Modelview”);

glUniformMatrix4fv(modelviewUniform, 1, 0, modelview);

 

现在是不是想知道为何OpenGL中的好多方法都由f或fv结尾。许多方法(如glUniform*)可以是浮点-指针参数的方法,可以是整形参数的方法,可是以其它类型参数的方法。OpenGL是C型的API,而C又不支持方法重载,所以每个方法得用不同的名字加以区分。表2.4 “OpenGL ES 方法后缀”,是方法的后缀的解释。随便说一下,v表示是一个指针型参数。

表2.4 OpenGL ES方法后缀

后缀

类型

i

32位整形

x

16位定点

f

32位浮点

ub

8位无符号byte

ui

32位无符号整形

 

ES 1.1提供了另外一个方法,可以使矩阵相乘,而ES2.0中没有这种方法。下面的代码首先加载了一个单位矩阵,然后再与其它两个矩阵相乘。

    float view[16] = { … };

float model[16] = { … };

 

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

glMultMatrixf(view);

glMultMatrixf(model);

 

模型-视图与投影矩阵默认是单位矩阵。单位矩阵用于恒等变换,即变换不起作用。见方式程2.2  恒等变换。

 

方程式 2.2 恒等变换

 

注意

关于矩阵与向量,矩阵与矩阵相乘,请参看附录A,C++向量库

 

本书中一律用行向量进行计算。方程式2.2中,左边的(vx vy vz 1)与右边的(vx*1 vy*1 vz*1 1) 都是四维向量。该方式程可以用列向量表示为:

 

很多情况下,将4维的行向量想像成1*4的矩阵,或把4维的列向量想像成4*1的矩阵会更容理解。(n*m表示矩阵的维数,其中n表示有多少行,m表示有多少列。)

图2.6 “矩阵相乘”展示了两个矩阵相乘的条件:中间两个维数一定要相等。外面的两个数字决定矩阵相乘的结果维数。利用这条规则,我们来验证方程式2.2中的合法性。*号右边的四维行向量(等价于1*4的矩阵)与右边的4*4的矩阵相乘的结果应是1*4的矩阵(同样的适用于四维列向量)。

 

图2.6矩阵相乘

 %title插图%num

从编码的角度来说,我发现行向量比列向理更理想,因行向量更像c语言中的数组。当然,只发你愿 意,你也可以用列向量,但是如果用列向量的话,你的变换顺序将要颠倒顺序才行。由于矩阵相乘不具有交换性,所以顺序很重要。

例如ES 1.1的代码:

      glLoadIdentity();

glMultMatrix(A);

glMultMatrix(B);

glMultMatrix(C);

glDrawArrays(…);

 

如果用行向量的方式,你可以把每次变换看成当前变换的pre-multiplied。那么上面的代码等效于:

%title插图%num

如果用列向量的方式,每次变换是post-multiplied。代码等效于:

%title插图%num

无论你用的是行向量还是列向量的方式,我们只需要记住一点,就是代码中*后的变换是*先作用于顶点变换的。为了更明确,那么将上面的列向量变换方式,用加括号的方式显示的展示变换的作用顺度。

%title插图%num

 

由于OpenGL的反向作用的特性,便用行向量会使其展现得更明显,这也是我为何喜欢用它的另一个原因。

关于数学方面的就介绍到此,现在回到摄影抽象,看看它是如何对应到OpenGL中来的。OpenGL ES 1.1提供了方法来生成矩阵,并在其当前矩阵中乘以新的变化矩阵一步完成新的变化。在后面小节中会介绍每一个方法。而ES 2.0没有这些方法,但是我会说明它的背后原理,让你自己实现这方法。

回忆一下OpenGL中用到的三个矩阵

1.   调整视角与视野,由投影矩阵作用。

2.   设置相机位置与朝向,由视图矩阵作用。

3.   缩放,旋转,移动每个对象,由模形矩阵作用。

我们将逐一介绍这三种变换,并完成一个*简单的变换流程。

设置模型矩阵

 

将一个对象放于场景中通常需要经过缩放,旋转,移动处理。

缩放

内置API是glScalef

    float scale[16] = { sx, 0,  0,  0,

0,  sy, 0,  0,

0,  0,  sz, 0

0,  0,  0,  1 };

 

// The following two statements are equivalent.下面两种方法等效

glMultMatrixf(scale);

glScalef(sx, sy, sz);

 

缩放矩阵与数学理论见方程式2.3

方程式2.3 缩放变换

%title插图%num

 

图2.7展示了 sx = sy = 0.5时的缩放变换

图2.7缩放变换

 %title插图%num

警告

当缩放因子x,y,z三个都不相等的情况,我们称之为非均匀缩放。这种方式的缩放是被完全允许的,但是大多数情况下会影响效率。因为一旦有非均匀缩放,OpenGL就会进行大量的灯光计算。

 

移动

 

glTranslatef可以轻松实现移动,将对象移动因定长度:

    float translation[16] = { 1,  0,  0,  0,

0,  1,  0,  0,

0,  0,  1,  0,

tx, ty, tz, 1 };

 

// The following two statements are equivalent.下面两种方法等效

glMultMatrixf(translation);

glTranslatef(tx, ty, tz);

 

简单的说,移动就是用加法实现的,要记住在齐次坐标中,我们可以用矩阵相乘的方式表达所有的变换,参看方程式2.4

 

 

方程式2.4 移动变换

 %title插图%num

 

图2.8描绘不当tx = 0.25and ty = 0.5时的移动变换

 

图2.8移动变换

 %title插图%num

旋转

 

还记得HelloArrow示例中,固定渲染通道(ES 1.1)下的移动吗?

 

glRotatef(m_currentAngle, 0, 0, 1);

 

这样就会绕着z轴逆时针旋转m_currentAngle度。*个参数表示旋转角度,后面三个参数表示旋转轴。在ES2.0的实现中,旋转就有点复杂,因为它是手工计算矩阵的:

 

    #include <cmath>

float radians = m_currentAngle * Pi / 180.0f;

float s = std::sin(radians);

float c = std::cos(radians);

float zRotation[16] = { c, s, 0, 0,

-s, c, 0, 0,

0, 0, 1, 0,

0, 0, 0, 1 };

 

GLint modelviewUniform = glGetUniformLocation(m_simpleProgram, “Modelview”);

glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);

 

图2.9 描绘了旋转45度的变换

 

 

图2.9 旋转变换

 %title插图%num

        绕着z轴旋转是非常简单的,但是如果绕着任意轴旋转就需要一复杂的矩阵。对于ES1.1, glRotatef可以帮我们自动生成矩阵,所以不必过多关心相关的概念。对于ES2.0,参看附录A, C++向量库,窥探其实现。

 

glRotatef只能通过其原点旋转,如果你想绕任意点旋转,你可以通过下面三步实现:

1.    移动-p。

2.    旋转。

3.    移动+p。

如果想改HelloArrow在(0, 1)点绕z轴旋转,那么可以如下修改:

 

     glTranslatef(0, +1, 0);

glRotatef(m_currentAngle, 0, 0, 1);

glTranslatef(0, -1, 0);

glDrawArrays(…);

 

记住,代码中*后的变换,在实现作用的时候是*先起效的!

 

设置视图变换

 

设置视图矩阵*简单的方法就是用LookAt方法,它并不是OpenGL ES的内置函数,但是可以自已快速实现。它有三个参数:相机位置,目标位置,一个”up”向量表示相机朝向(见图2.10 “LookAt 变换”)。

图2.10 LookAt 变换

 %title插图%num

        通过三个向量的传入,LookAt就可以生成一个变换矩阵,否则就得用基本的变换(缩放,移动,旋转)来生成。示例2.1 是LookAt的实现。

 

示例2.1 LookAt

mat4 LookAt(const vec3& eye, const vec3& target, const vec3& up)

{

vec3 z = (eye – target).Normalized();

vec3 x = up.Cross(z).Normalized();

vec3 y = z.Cross(x).Normalized();

 

mat4 m;

m.x = vec4(x, 0);

m.y = vec4(y, 0);

m.z = vec4(z, 0);

m.w = vec4(0, 0, 0, 1);

 

vec4 eyePrime = m * -eye;

m = m.Transposed();

m.w = eyePrime;

 

return m;

}

 

注意,示例2.1中用了自定义类型,如vec3,vec4,mat4。关非伪代码,而是用到了附录A,C++向量库中的代码。本章后面内容会详细介绍这个库。

 

设置投影变换

 

到此为止,我们已能修改模型-视图的变换。对于ES1.1我们可以用glRotatef与glTranslatef来影响当前矩阵,也可以用glMatrixMode在任意时刻来修改矩阵。初始化选中的是GL_MODELVIEW模式。

 

到底设影矩阵与模形-视图矩阵的区别是什么?对于OpenGL开发新手,会把投影想像为”camera matrix”,这种想法即使不算错,也是过于简单了,因为相机的位置与朝向是由模型-视图矩阵标识的。我更喜欢把投影想像成相机的“聚焦”,因为它可以控制视野。

 

警告

相机的位置与朝向是由模型-视图矩阵决定的,并非投影矩阵决定。在OpenGL ES 1.1中灯光计算的时候会用到这些数据。

 

在计算机图形学中有两种类型的投影方式:透视投影与正交投影。采用透视投影,物体越远越小,这样更接具真实性。图2.11“投影类型” 中可以看到它们的区别。

 

图2.11 投影类型

 %title插图%num

        正交投影往往用于2D绘制,所以在Hello Arrow中用了它:

 

const float maxX = 2;

const float maxY = 3;

glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1);

 

glOrthof的六个参数表示六面体每一面到原点的矩离,分别是:前,后,左,右,上,上。示例中参数的比例是2:3,这是因为iPhone的屏是320*480。 而ES 2.0 生成正交投影矩阵的方法是:

 

float a = 1.0f / maxX;

float b = 1.0f / maxY;

float ortho[16] = {

a, 0,  0, 0,

0, b,  0, 0,

0, 0, -1, 0,

0, 0,  0, 1

};

 

当正交投影的中心点位于原点的时候, 生成的投影矩阵类似于缩放矩阵,关于缩放矩阵,前面已介绍过。

 

sx = 1.0f / maxX

sy = 1.0f / maxY

sz = -1

 

float scale[16] = { sx, 0,  0,  0,

0,  sy, 0,  0,

0,  0,  sz, 0

0,  0,  0,  1 };

 

由于Hello Cone(本章示例,后面将看到)是绘制的3D图形,于是我们用glFrustumf来设置一个投影矩阵,这样写:

 

glFrustumf(-1.6f, 1.6, -2.4, 2.4, 5, 10);

 

glFrustumf的参数与glOrthof的一样。由于glFrustum在ES 2.0中不存在, 所以Hello Cone的ES2.0的实现就得自己计算矩阵,方法如下:

 

void ApplyFrustum(float left, float right, float bottom,

float top, float near, float far)

{

float a = 2 * near / (right – left);

float b = 2 * near / (top – bottom);

float c = (right + left) / (right – left);

float d = (top + bottom) / (top – bottom);

float e = – (far + near) / (far – near);

float f = -2 * far * near / (far – near);

 

mat4 m;

m.x.x = a; m.x.y = 0; m.x.z = 0; m.x.w = 0;

m.y.x = 0; m.y.y = b; m.y.z = 0; m.y.w = 0;

m.z.x = c; m.z.y = d; m.z.z = e; m.z.w = -1;

m.w.x = 0; m.w.y = 0; m.w.z = f; m.w.w = 1;

 

glUniformMatrix4fv(projectionUniform, 1, 0, m.Pointer());

}

 

一旦设置了设影矩阵, 就设定了视野。视锥表示眼在金字塔顶部的一个锥体(参看图2.12 视锥)

 

2.12 视锥

 %title插图%num

 

基于金字塔的顶点(称为视野)的角度,可以计算一个视锥。开发者认为这样比指定六面更加直观。示例2.2中方法有四个参数:视角,金字塔宽高比,远与近裁剪面。

 

示例 2.2 VerticalFieldOfView

void VerticalFieldOfView(float degrees, float aspectRatio,

float near, float far)

{

float top = near * std::tan(degrees * Pi / 360.0f);

float bottom = -top;

float left = bottom * aspectRatio;

float right = top * aspectRatio;

 

glFrustum(left, right, bottom, top, near, far);

}

告诫

设置投影的时候,应避免把远近裁剪面设为0或负数。数学上不支持这种工作方式。

 

用矩阵栈存取变换

还记得在用ES1.1实现HelloArrow的时候用glPushMatrix与glPopMatrix来存取变换的状态吗?

 

void RenderingEngine::Render()

{

glPushMatrix();

glDrawArrays(GL_TRIANGLES, 0, vertexCount);

glPopMatrix();

}

 

用Push/Pop这样的方式来实现Render是非常普遍日,因为这样的好外是可以阻止帧与帧这间变换的累积。

 

上面的示例,栈没有超过两层,iPhone允许嵌套6层栈。这样使复杂变化变得简单,比如渲染图2.13 “机器人手臂”这种有关节的对象,或者是会层次的模型。在用push/pop写代码的时候,*好有相应的缩进,如示例2.3“分层变换”

 

示例2.3 分层变换

void DrawRobotArm()

{

glPushMatrix();

glRotatef(shoulderAngle, 0, 0, 1);

glDrawArrays( … ); // upper arm

glTranslatef(upperArmLength, 0, 0);

glRotatef(elbowAngle, 0, 0, 1);

glDrawArrays( … ); // forearm

glTranslatef(forearmLength, 0, 0);

glPushMatrix();

glRotatef(finger0Angle, 0, 0, 1);

glDrawArrays( … ); // finger 0

glPopMatrix();

glPushMatrix();

glRotatef(-finger1Angle, 0, 0, 1);

glDrawArrays( … ); // finger 1

glPopMatrix();

glPopMatrix();

}

 

图2.13 机器人手臂

 %title插图%num

        每一个矩阵模式都有自己的栈,如图2.14“矩阵栈”,用得*多的是GL_MODELView。对于GL_TEXTURE模式的栈,我们会在另一章节中介绍。先前说过,OpenGL中的每一个项点位置变换都由当前的模型-视图矩阵与投影矩阵决定,也就是说在它们各自的栈中,它们位于栈顶。用glMatrixMode实现从一个栈模式到另一个模式。

 

 

图2.14 矩阵栈

%title插图%num

        在ES 2.0中不存在矩阵栈,如果你需要,你可以在你自已应用中加代码实现,也可用自己的数学库。这样是不是觉得ES2.0更难呀? 但你得记住ES 2.0 是一种”closerto te metal”的API, 利用shader它可以让你更自由更充分的操控图形。

 

动画

到现在,我们已看到了OpenGL执行背后的数学支持。由于OpenGL是一个低级别的图形API,并不是动画API。幸运的是,对于动画所需的数学非常简单。

 

用五个字来总结它:animationis all about interpolation(动画与插值相关)。一个应用程序的动画系统往往需要艺术家,用户或算法设定一些关键帧。然后在运行的时候,计算这些关键帧间的值。被当做关帧的数据可以是任意类型,常规是颜色,位置,角度。

 

插值技术

 

计算两关键帧中间帧的过程叫着补间。如果你将流逝时间除以动画时间,你就可以得到一个[0,1]的权值。如图2.15中所描绘 “缓动方式:线性,二次缓进,二次缓进-缓出”, 我们会讨论三种缓动方程式。对于补间值t,可以用如下方式计算插值:

 

float LinearTween(float t, float start, float end)

{

return t * start + (1 – t) * end;

}

 

某些类型的动画,不能用线性补间的方式实现,用Robert Penner的缓动方程可以让动画更加直实。该缓进的计算是相当简单:

 

float QuadraticEaseIn(float t, float start, float end)

{

return LinearTween(t * t, start, end);

}

 

Penner的 “二次缓进-缓出”方式有点复杂,但是把它分拆分开就变得简单了,见示例2.4。

 

示例2.4 二次缓进-缓出

 

float QuadraticEaseInOut(float t, float start, float end)

{

float middle = (start + end) / 2;

t = 2 * t;

if (t <= 1)

return LinearTween(t * t, start, middle);

t -= 1;

return LinearTween(t * t, middle, end);

}

 

图2.15缓动方式:线性,二次缓进,二次缓进-缓出

 %title插图%num

旋转动画与四元数

 

对于位置与颜色的关键帧,它们很容易插值:对于xyz或rgb分量,分别调用上面的的补间方法求补间值。角度也应一样处理,求角度的补间值而已。但是对于旋转轴不同的情况,如何计算这两个朝向的补间值呢?

 

在图2.3中,这个例子是在一个平面上(泽注:只能绕Z轴旋转),如果你的需要是每个节点是一个球(泽注:可以360度旋转)。那么每一个节点只存旋转角度是不够的,还要存旋转轴。我们将它标记为轴-角度,于是对于每一个节点需要4个浮点值。

 

原来有一个更简单的方法来表示一个任意旋转,与轴-角度的一样需要4个分量,这种方法更适合又插值。这个方法就是用四维向量组成的四元数,它于1843年被设想出来的。在现在矢量代数中,四元数的点被忽视,但经历计算机图形的发展,它得于复兴。 Ken Shoemake 是20世纪80年代末著名slerp方程的推广之一,而slerp方程可以计算两个四元数补间值。

 

 

知道

Shoemake的方程只是众多四元数插值的方法中的一种,但是它是*出名的,并在我们的向量库中所采用。其它的方法,如normalized quaternion lerp, log-quaternion lerp, 有时候在性能方面更理想。

 

说得差不多了,但你得明确,四元数并不是处理动画的*好的方式。有些时候,只需要简单的计算两个向量的夹角,找出一个旋转轴,并计算角度的插值即可。但是四元数解决了稍微复杂的问题,它不再是两个向时间的插值,而变成两个朝向间的插值。这样看起来更加迂腐,但是它有很重要的区别的。将你的手臂伸直向前,掌心向上。弯曲你的胳膊并旋转你的手,这样你就模仿了两个四元数的插值。

 

在我们的示例代码中用到了许多“轨迹球”旋转,用四元数来完成再合适不过了。在此我不会涉及大量枯燥的方程式,你可以到附录A,C++向量库去看四元数的实现。在HelloCone示例中与下一章中的wireframe view示例中,将会用到这个向量库。

 

用C++优化向量

在Hello Arrow中的顶点数据结构是:

struct Vertex {

float Position[2];

float Color[4];

};

 

如果我们继续沿用C数组的方式贯穿全书,你将会发现生活是多么的糟糕! 我们真正想要的应是这样:

 

struct Vertex {

vec2 Position;

vec4 Color;

};

这正是C++运算符重载与类模版强大的地方。运用C++可以让你写一个简单的库(其实,很简单)并使你应用的代码像是基于向量的一种语言开发。其实本书中的示例就是这样的。我们的库只包括了三个头文件,没有一个cpp文件:

Vector.hpp

定义了一套三维,三维,四维向量,可以是浮点也可以是整型。并没有依附任何头文件。

Matrix.hpp

定义了2×2, 3×3, 与 4×4矩阵类,只依附了Vector.hpp。

Quaternion.hpp

定义了四元数的类,并提供了构造与插值的方法,依附Matrix.hpp。

 

在附录A,C++向量库中包括了这些文件,但是还是向你展示一下本库是如何构成的,示例2.5是Vector.hpp的一部份。

示例 2.5 Vector.hpp

#pragma once

#include <cmath>

 

template <typename T>

struct Vector2 {

Vector2() {}

Vector2(T x, T y) : x(x), y(y) {}

T x;

T y;

};

 

template <typename T>

struct Vector3 {

Vector3() {}

Vector3(T x, T y, T z) : x(x), y(y), z(z) {}

void Normalize()

{

float length = std::sqrt(x * x + y * y + z * z);

x /= length;

y /= length;

z /= length;

}

Vector3 Normalized() const

{

Vector3 v = *this;

v.Normalize();

return v;

}

Vector3 Cross(const Vector3& v) const

{

return Vector3(y * v.z – z * v.y,

z * v.x – x * v.z,

x * v.y – y * v.x);

}

T Dot(const Vector3& v) const

{

return x * v.x + y * v.y + z * v.z;

}

Vector3 operator-() const

{

return Vector3(-x, -y, -z);

}

bool operator==(const Vector3& v) const

{

return x == v.x && y == v.y && z == v.z;

}

T x;

T y;

T z;

};

 

template <typename T>

struct Vector4 {

};

 

typedef Vector2<int> ivec2;

typedef Vector3<int> ivec3;

typedef Vector4<int> ivec4;

 

typedef Vector2<float> vec2;

typedef Vector3<float> vec3;

typedef Vector4<float> vec4;

 

我们把向量类型用C++模版的方式参数化了,这样一来就可以用相同代码成生基于浮点与定义的向量了。

虽然2维向量与3维向量有许多共同点,但是我们还是不能共用一套模版。我不能过过将维数参数化的模版来实现,如下面代码:

template <typename T, int Dimension>

struct Vector {

T components[Dimension];

};

 

当设计一个向量库的时候,在通用性与可读性上一定要有一个适当的平衡点。由于在向量类中逻辑相对较少,并很少需要遍历向量成员,分开定义类看起来是一个不错的选择。比如Position.y就比Position[1]更容易让读者理解。

 

由于向量这些类型会被常常用到,所以在示例2.5的底部用typedefs定义了一些缩写的类型。这些小写的名字如vec2,ivec4虽然打破了我们建立的命名规则,但是看起来的感觉就更接近语言本身的原生类型。

 

在我们的向量库中,vec2/ivec2这样的命名是借鉴GLSL中的关键字的。注意区分本书中C++部分与shader部分内容,不要混淆了。

 

提示

在GLSL着色语言中,vec2与mat4这些类型是语言内置类型。我们的C++向量库是模仿着它写的。

 

ES1.1实现Hello Cone

现在我们开始修改HelloArrow为Hello Cone。我们要改的不只是把内容从2D变为3D,我们还要支持两个新的朝向,当设备朝上或朝下。

 

本章示例与上一章的视觉上的变化很大,主要是修改RenderingEngine2.cpp与RenderingEngine2.cpp。由于前面章节中有了良好的接口设计,现在是它发挥作用的时候了。首先来处理ES 1.1 renderer, 即RenderingEngine1.cpp。

 

RenderingEngine 声明

 

表2.5 “HelloArrow与Hello Cone的不同之处” 指出了HelloArrow 与Hello Cone实现的几项差异。

 

表2.5 Hello Arrow与Hello Cone的不同之处

Hello Arrow

Hello Cone

绕着z轴旋转 四元数旋转
一个绘制方法 两个绘制方法,一个绘底,一个绘锥
C数组方式表示向量 用vec3的对像表示向量
三角形的数据小,由代码硬编码 运行时生成三角形的数据
三角形的数据存于C数级中 三角形的数据存于STL 向量中

 

 

我决定在本书示例中运用C++ STL(标准模版库)。运用它可以简化许多工作量,如它提供了可扩展的数组(std::vector)与双向链表(std::list)。许多的开发者都反对在移动设备如iPhone上写有时实性要求的代码时用STL开发。乱用STL的确会使你应用的内存无法控制,但如今,C++编译器对STL代码做了许多译化。同时我们得注意iPhone SDK也提供了一套Objective-C类(如,NSDictionary),这些类类似于STL的一些类,它们的内存占用率与性能都差不多。

 

它们的区别做到了心中有数 如表2.5, 再来看看RenderingEngine1.cpp的项部, 见示例2.6(注意 在这儿定义了新的顶点数据结构,因此你可以移除旧版本的数据结构)。

 

注意

如果你想边看边写代码,那么请在Finder中复制一份HelloArrow的工程目录,并改名为HelloCone。然后用Xcode打开,并在Project菜单中选择Rename,将工程名改为HelloCone。接着把附录A,C++向量库中的Vector.app,Matrix.hpp与Quaternion.hpp添加到工程。RenderingEngine1.cpp是区别*大的地方,打开它删掉里面所有内容,并修改为你将要看到的内容。

 

示例 2.6 RenderingEngine1 类定义

#include <OpenGLES/ES1/gl.h>

#include <OpenGLES/ES1/glext.h>

#include “IRenderingEngine.hpp”

#include “Quaternion.hpp”

#include <vector>

 

static const float AnimationDuration = 0.25f;

 

using namespace std;

 

struct Vertex {

vec3 Position;

vec4 Color;

};

 

struct Animation {    //[1]

Quaternion Start;

Quaternion End;

Quaternion Current;

float Elapsed;

float Duration;

};

 

class RenderingEngine1 : public IRenderingEngine {

public:

RenderingEngine1();

void Initialize(int width, int height);

void Render() const;

void UpdateAnimation(float timeStep);

void OnRotate(DeviceOrientation newOrientation);

private:

vector<Vertex> m_cone;     //[2]

vector<Vertex> m_disk;     //[3]

Animation m_animation;

GLuint m_framebuffer;

GLuint m_colorRenderbuffer;

GLuint m_depthRenderbuffer;

};

 

1.     动画结构,用于生成平滑的三维动画。包括三个表示方向的四元数:开始,当前插值,结束;还有两个时间跨度:经过的与持继时间,都是以秒为单位。它们是用来计算[0,1]的。

2.     三角形数据用两个STL容器保存,分别是m_cone与m_disk。向量容器是正确的选择,因为我们知道它有多大,它还能保证空间是连继的。储存顶点的空间必须是连继的,这是OpenGL所要求的。

3.     与Hello Arrow的不同外,这儿需要两个renderbuffers。Hello Arrow是二维的,所以只需要一个颜色renderbuffer。Hello Cone需要一个存深度信息的renderbuffer。在后面的章节会学习深度缓冲,在此只需要简单理角为:它是一个特殊的平面图像,用来存放每一个像素z值的结构。

 

OpenGL 初始化与锥体的镶嵌

 

在Hello Arrow中构造方法非常简单:

IRenderingEngine* CreateRenderer1()

{

return new RenderingEngine1();

}

 

RenderingEngine1::RenderingEngine1()

{

// Create & bind the color buffer so that the caller can allocate its space.

glGenRenderbuffersOES(1, &m_renderbuffer);

glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffer);

}

 

示例2.7中的Initialize方法,生成了顶点数据并创建了framebuffer。开始处定义了一些锥体的半径,高度与几何精细度。这儿几何精细度是指在垂直方向上锥体的分片数量。生成顶点数据后,初始化了OpenGL的framebuffer与相关变换矩阵。还开启了深度测试,因为这是一个真3D应用,在第四章会介绍更多的深度测试知识。

 

示例2.7 RenderingEngine 中的Initialize

void RenderingEngine1::Initialize(int width, int height)

{

const float coneRadius = 0.5f;     //[1]

const float coneHeight = 1.866f;

const int coneSlices = 40;

 

{

// Generate vertices for the disk.

}

 

{

// Generate vertices for the body of the cone.

}

 

// Create the depth buffer.

glGenRenderbuffersOES(1, &m_depthRenderbuffer);   //[2]

glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_depthRenderbuffer);

glRenderbufferStorageOES(GL_RENDERBUFFER_OES,

GL_DEPTH_COMPONENT16_OES,

width,

height);

 

// Create the framebuffer object; attach the depth and color buffers.

glGenFramebuffersOES(1, &m_framebuffer);     //[3]

glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer);

glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,

GL_COLOR_ATTACHMENT0_OES,

GL_RENDERBUFFER_OES,

m_colorRenderbuffer);

glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,

GL_DEPTH_ATTACHMENT_OES,

GL_RENDERBUFFER_OES,

m_depthRenderbuffer);

 

// Bind the color buffer for rendering.

glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_colorRenderbuffer);  //[4]

 

glViewport(0, 0, width, height);  //[5]

glEnable(GL_DEPTH_TEST);   //[6]

 

glMatrixMode(GL_PROJECTION);  //[7]

glFrustumf(-1.6f, 1.6, -2.4, 2.4, 5, 10);

 

glMatrixMode(GL_MODELVIEW);

glTranslatef(0, 0, -7);

}

        示例2.7是处理OpenGL的一个标准流程,在以后的内容中你会慢慢明白这一切。现在,简要说明一下:

1.     定义一些常量,用来生成顶点锥底与锥面的顶点数据。

2.     为深度缓冲生成一个id,绑定它,并为之分配存储空间。在在后面的深度缓冲中详细介绍。

3.     为缓冲对象生成id,绑定之,并把深度与颜色缓冲用glFramebufferRenderbufferOES依附于它。

4.     绑定颜色缓冲,后继的绘制将作用于它。

5.     设置viewport的左下角,长,宽属性。

6.     为3D场景开启深度测试

7.     设置投影与模型-视图矩阵

 

示例2.7中,两处生成顶点的地方都用省略号表示,是由于这两个地方值得深入分析。将对象拆分为三角形术语叫三角化,但常常也叫关镶嵌,它关系到多边形填充表面的边界问题。任何一个M.CEscher迷都知道,镶嵌是一个有趣的难题; 后面章节也会有介绍。

 

如图2.16 “HelloCone的镶嵌”,我们将锥面用triangle strip表示,锥底用trianglefan表示。

 

图2.16 Hello Cone的镶嵌

 %title插图%num

 

无论用strip还是fan模式,我们都可以成生锥面,但是fan模式的时候看起来会很奇怪。因为fan模式下,它的中心颜色是不正确的。就算我们为其中心指定一个颜色,在垂直方向上的将有不正确的放射效果,如图2.17 “左:triangle fan模式的锥体,右:triangle strip模式的锥体”

 

图2.17左:trianglefan模式的锥体,右:triangle strip模式的锥体

%title插图%num

        用strip的模式并不是生成锥面的*好方法,因为思维的时候三角形有退化过程(如图2.16中。 译注:上面的顶点不断退化为一个点的时候,就成锥体了)。用GL_TRINGLES的模式可以解决这个问题,但是需要两倍空间的顶点数组。由于OpenGL提供了一种基于索引的机制来解决这个顶点数组重复的问题,所以可以解决空间变大的问题,以后面的章节会介绍。现在我们还是用GL_TRIANGLE_STRIP来实现。生成锥体顶点的代码见示例2.8,生成过程原理见图2.18(将代码放在RenderingEngine::Initialize中//Generatevertices for the body of the cone的后面)。每一个切片需要两个顶点(一个顶点,一个底边弧上的点),还需要附加的切片来结束循环(图2.18)。于是总共的顶点数是(n+1)*2,其中n表示切片数。计算底边弧上点,采用绘制圆的经典算法即可, 如果你还记得三角函数,那对此一定觉得面熟的。

 

图2.18 Hello Cone顶点序列

 %title插图%num

示例 2.8 成生锥顶点

m_cone.resize((coneSlices + 1) * 2);

 

// Initialize the vertices of the triangle strip.

vector<Vertex>::iterator vertex = m_cone.begin();

const float dtheta = TwoPi / coneSlices;

for (float theta = 0; vertex != m_cone.end(); theta += dtheta) {

 

// Grayscale gradient

float brightness = abs(sin(theta));

vec4 color(brightness, brightness, brightness, 1);

 

// Apex vertex

vertex->Position = vec3(0, 1, 0);

vertex->Color = color;

vertex++;

 

// Rim vertex

vertex->Position.x = coneRadius * cos(theta);

vertex->Position.y = 1 – coneHeight;

vertex->Position.z = coneRadius * sin(theta);

vertex->Color = color;

vertex++;

}

 

在此我们用一种简单的方法创建了一个灰度渐变效果,这样可以模拟灯光:

 

float brightness = abs(sin(theta));

vec4 color(brightness, brightness, brightness, 1);

 

在这儿这个方法生成的颜色是固定的,在改变对象方向的时候是不会改变的,虽然有点遗憾,但是足以满足我们的当前需要。这种技术的术语是baked lighting,在第九章优化中会更会详细的介绍。关于更真实的灯光,在第四章中介绍。

 

示例2.9是生成锥底顶点的代码(将这代码放在RenderingEngine1::Initizlize中的//Generate vertices for the disk后面)。由于它用了trianglefan模式,所以总共的顶点数为:n+2, 多于的两个顶点,一个是中心点,一个是循环结束点。

 

示例2.9 生成锥底顶点

// Allocate space for the disk vertices.

m_disk.resize(coneSlices + 2);

 

// Initialize the center vertex of the triangle fan.

vector<Vertex>::iterator vertex = m_disk.begin();

vertex->Color = vec4(0.75, 0.75, 0.75, 1);

vertex->Position.x = 0;

vertex->Position.y = 1 – coneHeight;

vertex->Position.z = 0;

vertex++;

 

// Initialize the rim vertices of the triangle fan.

const float dtheta = TwoPi / coneSlices;

for (float theta = 0; vertex != m_disk.end(); theta += dtheta) {

vertex->Color = vec4(0.75, 0.75, 0.75, 1);

vertex->Position.x = coneRadius * cos(theta);

vertex->Position.y = 1 – coneHeight;

vertex->Position.z = coneRadius * sin(theta);

vertex++;

}

 

3D中平滑旋转

 

为了让动画平滑,在UpdateAnimation中用四元数旋转的时候,引入了Slerp(泽注:插值相关)。当设备朝向发生变化的时候,OnRotate方法就开始新的动画序列。具体参看示例2.10,“UpdateAnimation()与OnRotate()”。

 

示例2.10 UpdateAnimation()与OnRotate()

void RenderingEngine1::UpdateAnimation(float timeStep)

{

if (m_animation.Current == m_animation.End)

return;

 

m_animation.Elapsed += timeStep;

if (m_animation.Elapsed >= AnimationDuration) {

m_animation.Current = m_animation.End;

} else {

float mu = m_animation.Elapsed / AnimationDuration;

m_animation.Current = m_animation.Start.Slerp(mu, m_animation.End);

}

}

 

void RenderingEngine1::OnRotate(DeviceOrientation orientation)

{

vec3 direction;

 

switch (orientation) {

case DeviceOrientationUnknown:

case DeviceOrientationPortrait:

direction = vec3(0, 1, 0);

break;

 

case DeviceOrientationPortraitUpsideDown:

direction = vec3(0, -1, 0);

break;

 

case DeviceOrientationFaceDown:

direction = vec3(0, 0, -1);

break;

 

case DeviceOrientationFaceUp:

direction = vec3(0, 0, 1);

break;

 

case DeviceOrientationLandscapeLeft:

direction = vec3(+1, 0, 0);

break;

 

case DeviceOrientationLandscapeRight:

direction = vec3(-1, 0, 0);

break;

}

 

m_animation.Elapsed = 0;

m_animation.Start = m_animation.Current = m_animation.End;

m_animation.End = Quaternion::CreateFromVectors(vec3(0, 1, 0), direction);

}

Render 方法

 

*后非常重要的是HelloCone的Render这个方法。它与Hello Arrow的方法类似,只不过它调用了两上绘制的方法,而且在glClear加入了深度缓冲的标志。

 

示例 2.11RenderingEngine1::Render()

void RenderingEngine1::Render() const

{

glClearColor(0.5f, 0.5f, 0.5f, 1);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

 

glEnableClientState(GL_VERTEX_ARRAY);

glEnableClientState(GL_COLOR_ARRAY);

 

mat4 rotation(m_animation.Current.ToMatrix());

glMultMatrixf(rotation.Pointer());

 

// Draw the cone.

glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &m_cone[0].Position.x);

glColorPointer(4, GL_FLOAT, sizeof(Vertex),  &m_cone[0].Color.x);

glDrawArrays(GL_TRIANGLE_STRIP, 0, m_cone.size());

 

// Draw the disk that caps off the base of the cone.

glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &m_disk[0].Position.x);

glColorPointer(4, GL_FLOAT, sizeof(Vertex), &m_disk[0].Color.x);

glDrawArrays(GL_TRIANGLE_FAN, 0, m_disk.size());

 

glDisableClientState(GL_VERTEX_ARRAY);

glDisableClientState(GL_COLOR_ARRAY);

 

glPopMatrix();

}

 

注意到rotation.Pointer()这个调用没?在我们的C++向量库中,向量与矩阵都有一个方法Pointer(),用于返回指向指一个元素的指针。 这样将更加方便传递参数到OpenGL。

 

注意

如果我们将用隐式转换的操作代替Pointer(),那么我们不可能使我们的OpenGL代码更加简洁,同样很容易出错,因为编译器具体做了什么,我们也不知道。出于类似的原因,STL中的string才提供c_str()这样的方法返回char*。

 

由于现在我们只实现了ES1.1的相关部份,所以在GLView.mm中得开启ForceES1。 这样你就可以编译运行你的*个真3D应用程序。为了看到新加入的两个朝向功能, 你可以将你iPhone放在头顶看,或放在腰间低头看。图2.19 “从左到右依次为:竖屏,上下颠倒,面向上,面向下,home按键在右的横屏,home按键在左的横屏”。

 

图2.19从左到右依次为:竖屏,上下颠倒,面向上,面向下,home按键在右的横屏,home按键在左的横屏

 %title插图%num

Shader实现的Hello Cone

对于RenderingEngine2.cpp的变化,我们不是将Hello Arrow中的复制过来做一些修改,而是将RenderingEngine1.cpp的内容复制过来,并运用ES2.0的技术来修改,这样会更有学习意义。只需要修改两处, 由于HelloArrow中RenderingEngine2.cpp中的BuildShader与BuildProgram方法仍然需要,于是将它们先保存起来,再修改engine1到engine2。示例2.12 “RenderingEnngine2类声明”是RenderingEngine2.cpp的代码。新加入或是修改的部份用粗体标识。由于一些不需要修改的部分是用…表示的,所以你不能直接复制下面的代码(只需要按粗体进行修改)。

 

示例2.12 RenderingEnngine2类声明

#include <OpenGLES/ES2/gl.h>

#include <OpenGLES/ES2/glext.h>

#include “IRenderingEngine.hpp”

#include “Quaternion.hpp”

#include <vector>

#include <iostream>

 

#define STRINGIFY(A)  #A

#include “../Shaders/Simple.vert”

#include “../Shaders/Simple.frag”

 

static const float AnimationDuration = 0.25f;

 

 

class RenderingEngine2 : public IRenderingEngine {

public:

RenderingEngine2();

void Initialize(int width, int height);

void Render() const;

void UpdateAnimation(float timeStep);

void OnRotate(DeviceOrientation newOrientation);

private:

GLuint BuildShader(const char* source, GLenum shaderType) const;

GLuint BuildProgram(const char* vShader, const char* fShader) const;

vector<Vertex> m_cone;

vector<Vertex> m_disk;

Animation m_animation;

GLuint m_simpleProgram;

GLuint m_framebuffer;

GLuint m_colorRenderbuffer;

GLuint m_depthRenderbuffer;

};

Initialize方法如下,但对于ES2.0不适用。

glMatrixMode(GL_PROJECTION);

glFrustumf(-1.6f, 1.6, -2.4, 2.4, 5, 10);

 

glMatrixMode(GL_MODELVIEW);

glTranslatef(0, 0, -7);

 

把它们改为:

 

m_simpleProgram = BuildProgram(SimpleVertexShader,

SimpleFragmentShader);

glUseProgram(m_simpleProgram);

 

// Set the projection matrix.

GLint projectionUniform = glGetUniformLocation(m_simpleProgram,

“Projection”);

mat4 projectionMatrix = mat4::Frustum(-1.6f, 1.6, -2.4, 2.4, 5, 10);

glUniformMatrix4fv(projectionUniform, 1, 0,

projectionMatrix.Pointer());

 

BuildShader与BuildProgram两个方法与Hello Arrow中的一样,于是在这儿不提供出来了。两个shader也一样,由于这儿是bakedlighting,所以只需要简单的传入颜色值即可。

 

在Render方法中设置模型-视图矩阵,参看示例2.13“RenderingEngine2::Render()”。记住,glUniformMatrix4fv与ES 1.1中的glLoadMatrix扮演的角色是一样的。

 

示例 2.13RenderingEngine2::Render()

void RenderingEngine2::Render() const

{

GLuint positionSlot = glGetAttribLocation(m_simpleProgram,

“Position”);

GLuint colorSlot = glGetAttribLocation(m_simpleProgram,

“SourceColor”);

 

glClearColor(0.5f, 0.5f, 0.5f, 1);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

glEnableVertexAttribArray(positionSlot);

glEnableVertexAttribArray(colorSlot);

 

mat4 rotation(m_animation.Current.ToMatrix());

mat4 translation = mat4::Translate(0, 0, -7);

 

// Set the model-view matrix.

GLint modelviewUniform = glGetUniformLocation(m_simpleProgram,

“Modelview”);

mat4 modelviewMatrix = rotation * translation;

glUniformMatrix4fv(modelviewUniform, 1, 0, modelviewMatrix.Pointer());

 

// Draw the cone.

{

GLsizei stride = sizeof(Vertex);

const GLvoid* pCoords = &m_cone[0].Position.x;

const GLvoid* pColors = &m_cone[0].Color.x;

glVertexAttribPointer(positionSlot, 3, GL_FLOAT,

GL_FALSE, stride, pCoords);

glVertexAttribPointer(colorSlot, 4, GL_FLOAT,

GL_FALSE, stride, pColors);

glDrawArrays(GL_TRIANGLE_STRIP, 0, m_cone.size());

}

 

// Draw the disk that caps off the base of the cone.

{

GLsizei stride = sizeof(Vertex);

const GLvoid* pCoords = &m_disk[0].Position.x;

const GLvoid* pColors = &m_disk[0].Color.x;

glVertexAttribPointer(positionSlot, 3, GL_FLOAT,

GL_FALSE, stride, pCoords);

glVertexAttribPointer(colorSlot, 4, GL_FLOAT,

GL_FALSE, stride, pColors);

glDrawArrays(GL_TRIANGLE_FAN, 0, m_disk.size());

}

 

glDisableVertexAttribArray(positionSlot);

glDisableVertexAttribArray(colorSlot);

}

 

示例2.13与示例2.11流程都差不多,只有细节不同。

 

接着,我们将该文件中所有RenderingEngine1修改为RenderingEngine2,包括工厂方法(修改为CreateRenderer2)。同样要去掉所有的_OES与OES。关闭GLView.mm中的ForceES1,这样基于shader 的Hello Cone就修改完成了。这样ES2.0的支持完成了,并没有添加任何酷的shader效果,让我们学到了两种不同API的区别。

 

结束语

本章是本书术语*多的一章,我们学习了一些基础图形学概念,交澄清了*章示例代码中掩盖的技术细节。

 

变换部份可能是*验理解的,也是OpenGL新手*攻克*关键的部份。我希望你能用Hello Cone来做实验,以便你更好的了解其工作原理。比如,硬编码旋转与移动,并观察顺序对渲染结果的影响。

在下一章你会学到用OpenGL绘制更复杂的图形,并初步涉及到iPhone触摸屏相关知识。

ios高效开源类库

MBProgressHUD(进展指示符库)
地址:https://github.com/jdg/MBProgressHUD
苹果的应用程序一般都会用一种优雅的,半透明的进度显示效果,不过这个API是不公开的,因此你要是用了,很可能被清除出AppStore。而 MBProgressHUD提供了一个替代方案,而且在用户角度上,实现的效果根本看不出和官方程序有什么差别。同时还提供了其他附加功能,比如虚拟进展 指示符,以及完成提示信息。整合到项目里也很容易,这里不细谈了。
ASIHttpRequest(HTTP Network库)
地址:http://allseeing-i.com/ASIHTTPRequest/
iPhone当然也有自己的HTTP Network API,那为什么要用ASIHttpRequest呢?因为官方的API简直跟话痨似的,太罗嗦了!ASIHttpRequest库*大的简化了网络通 信,提供更先进的工具,什么文件上传工具,重定向处理工具、验证工具、等等。只要你手头的东西跟HTTP有关,用这个*对能让你感觉道生活有美好!先看一 段代码就体会到了。
    (void) loadAppDevMag
    {
      NSURL *url = [NSURL URLWithString:@”http://www.appdevmag.com”];
      ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
      [request setDelegate:self];
      [request startAsynchronous];
    }
    – (void)requestFinished:(ASIHTTPRequest *)request
    {
      // Use when fetching text data
      NSString *responseString = [request responseString];
    }
JSON Framework(JSON支持)
地址:http://stig.github.com/json-framework/
如果你做的应用和网站服务器有交互,那就得用到JSON了。但事实上,iOS平台的原生类库根本就不支持JSON,这就略犀利了吧?不过JSON框 架满足了你的所有需求,包括一个解析器将JSON字符串解析成对象;以及一个生成器从对象生成字符串。这个库根本就是太流行了,JSON提过很多次了,具 体特点就不多讲了,所谓“一段代码胜千言”,下面用一段代码演示一下吧。
    // JSON string -> NSDictionary
    NSString *jsonString = @”{\”foo\”: \”bar\”}”;
    NSDictionary *dictionary = [jsonString JSONValue];
    NSLog(@”Dictionary value for \”foo\” is \”%@\””, [dictionary objectForKey:@”foo”]);
    // NSDictionary -> JSON string
    NSString *newJsonString = [dictionary JSONRepresentation];
Flurry(详尽的使用统计)
%title插图%num
地址:http://www.flurry.com/product/analytics/index.html
通过Furry你可以得到应用的用户人数,用户活跃度,用户来源等统计信息。但是他*厉害的地方是,你可以追踪应用本身的事件和错误记录,所有这些 数据都会在一个类似Google Analytics的界面上显示,这样就很容易掌握用户的行为和出现的问题。当然,这个星球上很多统计工具,但是这款是作者个人比较推崇的解决方案。
RegexKitLite(正则表达式支持)
地址:http://regexkit.sourceforge.net/RegexKitLite/
正则表达式大家都知道。但是iPhone SDK居然当他不存在?这怎么能忍啊!果断用RegexKitLite。虽然叫的是Lite,但是功能很full。示例代码。
    // finds phone number in format nnn-nnn-nnnn
    NSString *regEx = @”[0-9]{3}-[0-9]{3}-[0-9]{4}”;
    for(NSString *match in [textView.text componentsMatchedByRegex:regEx]) {
        NSLog(@”Phone number is %@”, match);
    }
Facebook iOS SDK(Facebook API类库)
%title插图%num
地址:https://github.com/facebook/facebook-ios-sdk
大体来讲就是iPhone上的Facebook login,完全支持Facebook Graph API和the older REST api。如果你的应用跟Facebook有关,相信我,用这个吧。
SDWebImage(简化网络图片处理)
地址:https://github.com/rs/SDWebImage
用SDWebImage调用网站上的图片,跟本地调用内置在应用包里的图片一样简单。操作也很简单,举例说明
[imageView setImageWithURL:[NSURL URLWithString:@”http://example.com/image.png”]];
类似的功能在Three20里也有,这个过会再说。相比而言,SDWebImage主要是提供一个小而精的简捷方便的解决方案
GData client(iPhone上所有Google相关服务的类库)
地址:http://code.google.com/p/gdata-objectivec-client/
名字就说明一切了。跟Google相关的,值得一提的是,这个项目很开放。有很多示例程序供下载。
CorePlot(2D图形绘图仪)
%title插图%num
地址:http://code.google.com/p/core-plot/
CorePlot有很多解决方案将你的数据可视。,同时也会提供各种迷人的图形效果,比如棒状图、饼状图、线状图等等,在他们网站上也提供了大量的范例图形,很多股票价格应用,游戏分数,个人财务管理都在用。
Three20(通用iOS库)
%title插图%num
地址:https://github.com/facebook/three20
Three20类库是Facebook自己做的,大而全是他*大的特色。把他整合到已有的项目中可能得费点周折,不过如果一开始你就用上了Three20,尤其是牵扯到很多web相关的项目的时候,你就能深刻体会到神马叫给力了。

KissXml——xml解析库
相关教程:http://www.iteye.com/topic/625849

http://sencho.blog.163.com/blog/static/83056228201151743110540/

很方便的一个xml解析器,支持Xpath查询。

 

skpsmtpmessage——Quick SMTP邮件发送
svn checkout http://skpsmtpmessage.googlecode.com/svn/trunk/ skpsmtpmessage-read-only
github:       git clone https://github.com/kailoa/iphone-smtp.git
相关教程:http://disanji.net/2011/01/28/skpsmtpmessage-open-source-framework/
skpsmtpmessage 是由Skorpiostech, Inc.为我们带来的一个SMTP协议的开源实现,使用Objective-c 实现,iOS系统的项目可以直接调用。

zxing——二维码扫描库
支持条形码/二维码扫描的图形处理库,这是一个java库,在android上的功能比较完整。同时该库也支持ios,但只能支持二位条形码的扫描。

 

kal——iPhone日历控件
一个类似于ios系统默认日历开源日历库,支持添加事件,自定义日历样式等功能。

shareKit——分享库
相关demo:http://www.cocoachina.com/bbs/read.php?tid-71760.html
分享到开心,豆瓣,腾讯,新浪微博的api所用到的强大的分享库。

 

FMDatabase——SQLite的Objective-C封装
是SQLite的C API對初學者來說實在太麻煩太瑣碎,難度太高。FMDB說穿了其實只是把C API包裝成簡單易用的Objective-C类。對于SQLite初學者來說,大大減低了上手的難度。有了FMDB,寫程式時只要專心在SQLite的語法上,而不用去理那堆有看沒有懂的C API,實在是件快樂的事情。

 

Panoramagl —— 720全景展示

Panorama viewer library for iPhone, iPad and iPod touch

iCarousel  —— 效果很酷的分页排列

内容类似的页面需要并排列出来,供用户选择。iCarousel具有非常酷的3D效果,比如经典的CoverFlow, TimeMachine。另外还具有线性,圆柱状等其它效果。可用于图片选择,书籍选择,网页选择等。

EGOTableViewPullRefresh —— 下拉列表刷新

使用这个库,很容易就可以实现下拉刷新效果。微博,RSS阅读器之类的软件经常使用。

CMPopTipView —— 泡泡风格的提示界面

一个泡泡风格的提示框开源控件, 继承自UIView。iPad,iPhone通用。

HMGLTransitions —— 视图切换动画

提供一些UIView或UIViewController切换时候的3D动画效果。比如翻转,开门,撕纸等。

QuickDialog —— 表格风格的配置界面

在iphone, 通常使用UITableView来创建一些配置,登录界面,创建这些界面通常很机械很烦人。QuickDialog可以快速地在表格项中放置开关 On/Off控件、日期控件、Sliders、单选按钮编辑框等等。这样就不用使用低级的UITableView。
 

JMTabView  —— 自定义标签栏和Tabbar

JMTabView是一个iOS自定义的标签栏开源控件,界面效果完全使用Core Graphics绘制,而没有用图片,所以内容很容易改为你需要的。
 

SBTableAlert —— 带表格视图的消息对话框

SBTableAlert对话框中提供了一种方式,在UIAlertView视图中包含了UITableView的效果,从而可以实现在UIAlertView中进行表格多选。使用简单。
 

EasyTableView —— 可水平或垂直滚动的TableView

原始的UITableView只可以垂直滚动。EasyTableView可以很方便的实现TableView的水平滚动, 并可重复实现用户自定义的View,就类似重复使用UITableViewCell, 这对于提高效率很有帮助。
 

MTStatusBarOverlay  —— 在状态栏上显示自定义的View

iOS程序通常会在*上面出现一个状态栏。使用这个库,可以很方便的在状态栏上显示一些信息,比如下载进度等。

 

 iOS-MagnifyingGlass  ——IOS放大镜效果

可以选择放大镜的样式,和放大倍数

Openear—— 语音识别和TTS

提供了语音识别和Text-to-speach 的接口

Google Toolbox for Mac(GTM)—— 从不同Google项目收集的代码

包含各种的工具类。比如字符串的base64及二进制编码解码, 系统版本号比较, 路径查找等等。每个工具类都比较独立, 可单独抽出来使用。

 

 

SFHFKeychainUtils(scifihifi-iphone)—— 保存用户密码到keychain中

为了用户安全,可以使用钥匙串Keychain来保存用户密码。SFHFKeychainUtils封装了钥匙串的访问, 读写,使用起来很方便。

 

MKStoreKit —— 程序内购买

程序内购买的流程的封装。

 

GLGestureRecognizer——手势识别器

封装了多种手势的识别器,例如三角形,长方形,圆形,五角星形等

 

扫描wifi信息:

http://code.google.com/p/uwecaugmentedrealityproject/

http://code.google.com/p/iphone-wireless/

tcp/ip的通讯协议:

http://code.google.com/p/cocoaasyncsocket/

 

voip/sip:

http://code.google.com/p/siphon/

http://code.google.com/p/asterisk-voicemail-for-iphone/

http://code.google.com/p/voiphone/

jabber client

http://code.google.com/p/ichabber/

 

PLBlocks

http://code.google.com/p/plblocks/

 

image processing

http://code.google.com/p/simple-iphone-image-processing/

 

base64编码解码:http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/?r=87

xml解析:https://github.com/schwa/TouchXML

 

异步加载图片并缓存代码:http://www.markj.net/iphone-asynchronous-table-image/

iphone TTS:https://bitbucket.org/sfoster/iphone-tts

iphone cook book 源码:https://github.com/erica/iphone-3.0-cookbook-

OAuth认证:  http://code.google.com/p/oauth/
http://code.google.com/p/oauthconsumer/

蓝牙协议栈:http://code.google.com/p/btstack/

语音识别:http://www.politepix.com/openears/

 

地球显示信息:http://code.google.com/p/whirlyglobe/

原文地址: http://www.cocoachina.com/iphonedev/toolthain/2011/1109/3480.html

 

如何用Facebook graphic api上传视频:

http://developers.facebook.com/blog/post/532/

对焦功能的实现:

http://www.clingmarks.com/?p=612

自定义圆角Switch按件:

https://github.com/domesticcatsoftware/DCRoundSwitch

弹出窗口For iphone and ipad:

https://github.com/chrismiles/CMPopTipView

KVO详解:

http://nachbaur.com/blog/back-to-basics-using-kvo

图片浏览:

https://github.com/bdewey/Pholio

Dropbox实例:

https://github.com/bdewey/dropvault

当地天气预报实例:

https://github.com/aspitz/WxHere

可伸缩的toolBar实例:

https://github.com/aspitz/ToolDrawer

app资源保护相关:

http://aptogo.co.uk/2010/07/protecting-resources/

cocos2d中也可用UIScrollView,UITableView,UIGestureRecognizers

https://github.com/jerrodputman/CCKit
http://www.tinytimgames.com/2011/08/05/introducing-cckit/

 

iOS文档导入导出:

http://mobiforge.com/developing/story/importing-exporting-documents-ios

 

CoreAnimation Demo:

https://github.com/bobmccune/Core-Animation-Demos

CoreAnimation Dev:

Part 1 – Frame By Frame Sprites With Core Animation

Part 2 – Space Time

Part 3 – Scrolling Hell

Part 4 – Parallax Scrolling

iOS jabber聊天应用开发:客户端开发

http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-interface-setup/

http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-custom-chat-view-and-emoticons/

iOS jabber聊天应用开发:服务器搭建

http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-server-setup/

 

KeyChain封装,安全存数据:

http://developer.apple.com/library/mac/#documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html
http://developer.apple.com/library/ios/#samplecode/GenericKeychain/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007797-Intro-DontLinkElementID_2

声音相关:

http://purplelilgirl.tumblr.com/post/9377269385/making-that-talking-app
http://dirac.dspdimension.com/Dirac3_Technology_Home_Page/Dirac3_Technology.html

弹珠游戏:

http://www.crowsoft.com.ar/wordpress/?p=19

spring boardUI:

https://github.com/rigoneri/myLauncher

MacOS&iOS upnp:

http://code.google.com/p/upnpx

ios block learn:

http://ios-blog.co.uk/iphone-development-tutorials/programming-with-blocks-an-overview/
https://github.com/zwaldowski/BlocksKit

弹出框中输入用户名与密码:

https://github.com/enormego/EGOTextFieldAlertView

 

jailbreak iphone发送sms:

http://code.google.com/p/iphone-sms/

搜索ituneappurl scheme:

https://github.com/Zwapp/schemes-scanner

横竖屏切换自动调整位置:

https://github.com/michaeltyson/TPMultiLayoutViewController

键盘出现与消失view自动移动避免遮挡:

https://github.com/michaeltyson/TPKeyboardAvoiding
http://atastypixel.com/blog/a-drop-in-universal-solution-for-moving-text-fields-out-of-the-way-of-the-keyboard/

iOS类似firebugweb调试工具:

http://phonegap.github.com/weinre/

一个UI开源库tapkulibrary,集成了calendar,coverflow

https://github.com/devinross/tapkulibrary

Tapku: An Amazing Open Source iOS Interface Library

多列的TableView

https://github.com/Xenofex/MultiColumnTableViewForiOS

mac的一个桌面程序,开源的

http://homepage.mac.com/nathan_day/pages/popup_dock.xml

PSTreeGraph for iPad

https://github.com/epreston/PSTreeGraph

文件预览like QLPreviewController

https://github.com/rob-brown/RBFilePreviewer

Interface Builder中用自定义字体解决方案

https://github.com/0xced/FontReplacer

shaderUILabe

https://github.com/nicklockwood/FXLabel

GCD学习

Cocoa Touch Tutorial: Using Grand Central Dispatch for Asynchronous Table View Cells

http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial

https://github.com/SlaunchaMan/GCDExample

UITableView中有search功能教程

How To Search Option Enable In TableView In iPhone

iPad阅读器开发

http://mobile.tutsplus.com/tutorials/iphone/building-an-ipad-reader-for-war-of-the-worlds/

http://mobile.tutsplus.com/tutorials/iphone/ios-sdk-using-a-slider-to-scrub-a-pdf-reader/

http://mobile.tutsplus.com/tutorials/iphone/ios-sdk-adding-a-table-of-contents-to-an-ipad-reader/

ipad UI 24个免费资源

http://www.cocoachina.com/applenews/devnews/2011/0915/3237.html

TableView的扩展

https://github.com/OliverLetterer/UIExpandableTableView

Orge3D for iOS

http://code.google.com/p/gamekit/

http://www.tonybhimani.com/2011/07/09/ogre3d-sdk-1-7-3-for-apple-iphone-ios-howto/

文档比Three20更全的类Three20

https://github.com/jverkoey/nimbus

iOS Boilerplate一个库集合,方便开发

http://iosboilerplate.com/

https://github.com/gimenete/iOS-boilerplate

openCV for iOS

http://code.google.com/p/edgy-camera-ios/

https://github.com/BloodAxe/opencv-ios-template-project

https://github.com/BloodAxe/OpenCV-iOS-build-script

http://computer-vision-talks.com/2011/02/building-opencv-for-iphone-in-one-click/

http://computer-vision-talks.com/2011/01/using-opencv-in-objective-c-code/

http://computer-vision-talks.com/2011/08/a-complete-ios-opencv-sample-project/

PageCurl for iOS

https://github.com/xissburg/XBPageCurl

https://github.com/raweng/FlipView

https://github.com/Split82/HMGLTransitions

http://api.mutado.com/mobile/paperstack/

iOS PDF实例

http://www.cocoachina.com/bbs/read.php?tid=75173

https://github.com/vfr/Reader

Core Animation

http://nachbaur.com/blog/core-animation-part-1

http://nachbaur.com/blog/core-animation-part-2

http://nachbaur.com/blog/core-animation-part-3

http://nachbaur.com/blog/core-animation-part-4

Core Data注意的地方

http://nachbaur.com/blog/smarter-core-data

http://iphonedevelopment.blogspot.com/2009/09/core-data-migration-problems.html

GCD

http://nachbaur.com/blog/using-gcd-and-blocks-effectively

http://deusty.blogspot.com/2011/01/multi-core-ios-devices-are-coming-are.html

MKMapView zoom level

http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/

http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/

HTML parser

Taming HTML Parsing with libxml (1)

https://github.com/topfunky/hpple

https://github.com/zootreeves/Objective-C-HMTL-Parser

openGLES

http://www.ityran.com/portal.php

Charts绘制开源库

http://code.google.com/p/core-plot/

https://github.com/ReetuRaj/MIMChart-Library   说明文档

apple 私有api文档

http://hexorcist.com/private_frameworks/html/main.html

safari的切换页面库

https://github.com/100grams/HGPageScrollView

自定义Slider组件

https://github.com/buildmobile/iosrangeslider

iOS Range Slider Part 1
iOS Range Slider Part 2

一些自定义组件:

自定义UIAlertView

自定义BadgeView

自定义数字键盘

QR Encoder二维码识别

https://github.com/jverkoey/ObjQREncoder

xml解析库

https://github.com/ZaBlanc/RaptureXML

wapper map for iOS

https://github.com/yinkou/OCMapView

iOS unitity

https://github.com/ZaBlanc/iBoost

https://github.com/escoz/QuickDialog/

socket

http://code.google.com/p/cocoaasyncsocket/

custom camera view

https://github.com/pmark/Helpful-iPhone-Utilities

http://www.codza.com/custom-uiimagepickercontroller-camera-view

本地天气demo

http://www.cocoachina.com/bbs/read.php?tid-72558-fpage-3.html

浏览器飞行动画

http://www.cocoachina.com/downloads/video/2011/1002/3313.html

切换动画demo

http://www.cocoachina.com/bbs/read.php?tid-76431-page-1.html

Automatic Reference Counting

http://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html

voip for ios development

http://trac.pjsip.org/repos/wiki/Getting-Started/iPhone

http://www.piemontewireless.net/PJSip155_and_iPhoneSDK312

http://code.google.com/p/siphon/

图像处理

http://www.cocoachina.com/downloads/code/2011/1009/3335.html

脚本自动生成push notification所需证书

https://github.com/jprichardson/GeneratePushCerts

自定义ActivityIndicator

https://github.com/hezi/HZActivityIndicatorView

开源库for ios

boost for iphone

ffmpeg for iphone

opencore amr for iphone

iOS网络相关

bonjour

ios 用xcode4.2开发 访问web service的功能

1。后台利用 cxf 构建一个web service服务。

  • HelloWorld.java
[java] view plain copy

  1. /**
  2.  * 
  3.  */
  4. package com.alcor.jws.test;
  5. import javax.jws.WebMethod;
  6. import javax.jws.WebService;
  7. import org.apache.cxf.feature.Features;
  8. /**
  9.  * @author 徐泽宇(roamer)
  10.  * 
  11.  *         2010-7-10
  12.  */
  13. @WebService
  14. @Features(features = “org.apache.cxf.feature.LoggingFeature”)
  15. public interface HelloWorld {
  16.     @WebMethod
  17.     String sayHi(String text);
  18.     @WebMethod
  19.     boolean userLogon(String username,String userpasswd);
  20. }
  1. /**
  2. *
  3. */
  4. package com.alcor.jws.test;
  5. import javax.jws.WebMethod;
  6. import javax.jws.WebService;
  7. import org.apache.cxf.feature.Features;
  8. /**
  9. * @author 徐泽宇(roamer)
  10. *
  11. * 2010-7-10
  12. */
  13. @WebService
  14. @Features(features = “org.apache.cxf.feature.LoggingFeature”)
  15. public interface HelloWorld {
  16. @WebMethod
  17. String sayHi(String text);
  18. @WebMethod
  19. boolean userLogon(String username,String userpasswd);
  20. }
  • HelloWorldImpl.java
[java] view plain copy

  1. /**
  2.  * 
  3.  */
  4. package com.alcor.jws.test;
  5. import org.apache.cxf.feature.Features;
  6. import org.apache.log4j.Logger;
  7. import javax.jws.WebMethod;
  8. import javax.jws.WebService;
  9. /**
  10.  * @author 徐泽宇(roamer)
  11.  *
  12.  * 2010-7-10
  13.  */
  14. @WebService
  15. @Features(features = “org.apache.cxf.feature.LoggingFeature”)
  16. public class HelloWorldImpl implements HelloWorld {
  17.     /**
  18.      * Logger for this class
  19.      */
  20.     private static final Logger logger = Logger.getLogger(HelloWorldImpl.class);
  21.         @WebMethod
  22.         public String sayHi(String text) {
  23.         if (logger.isDebugEnabled()) {
  24.             logger.debug(“sayHi(String) – start”); //$NON-NLS-1$ 
  25.         }
  26.         String returnString = “Hello,你好: “ + text;
  27.         if (logger.isDebugEnabled()) {
  28.             logger.debug(“返回内容:”+returnString);
  29.             logger.debug(“sayHi(String) – end”); //$NON-NLS-1$ 
  30.         }
  31.             return returnString;
  32.         }
  33.         @WebMethod
  34.         public boolean userLogon(String username ,String userpasswd)
  35.         {
  36.             logger.debug(“用户名是:”+username+“口令是:”+userpasswd);
  37.             if (username.equalsIgnoreCase(“admin”))
  38.             {
  39.                 return true;
  40.             }else{
  41.                 return false;
  42.             }
  43.         }
  44. }
  1. /**
  2. *
  3. */
  4. package com.alcor.jws.test;
  5. import org.apache.cxf.feature.Features;
  6. import org.apache.log4j.Logger;
  7. import javax.jws.WebMethod;
  8. import javax.jws.WebService;
  9. /**
  10. * @author 徐泽宇(roamer)
  11. *
  12. * 2010-7-10
  13. */
  14. @WebService
  15. @Features(features = “org.apache.cxf.feature.LoggingFeature”)
  16. public class HelloWorldImpl implements HelloWorld {
  17. /**
  18. * Logger for this class
  19. */
  20. private static final Logger logger = Logger.getLogger(HelloWorldImpl.class);
  21. @WebMethod
  22. public String sayHi(String text) {
  23. if (logger.isDebugEnabled()) {
  24. logger.debug(“sayHi(String) – start”); //$NON-NLS-1$
  25. }
  26. String returnString = “Hello,你好: “ + text;
  27. if (logger.isDebugEnabled()) {
  28. logger.debug(“返回内容:”+returnString);
  29. logger.debug(“sayHi(String) – end”); //$NON-NLS-1$
  30. }
  31. return returnString;
  32. }
  33. @WebMethod
  34. public boolean userLogon(String username ,String userpasswd)
  35. {
  36. logger.debug(“用户名是:”+username+“口令是:”+userpasswd);
  37. if (username.equalsIgnoreCase(“admin”))
  38. {
  39. return true;
  40. }else{
  41. return false;
  42. }
  43. }
  44. }
  • java 的web service 访问客户端
[cpp] view plain copy

  1. /**
  2.  * 
  3.  */
  4. package com.alcor.jws.test;
  5. import org.apache.cxf.interceptor.LoggingInInterceptor;
  6. import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
  7. /**
  8.  * @author 徐泽宇(roamer)
  9.  * 
  10.  *         2010-7-10
  11.  */
  12. public class Client {
  13.     private Client() {
  14.     }
  15.     public static void main(String args[]) throws Exception {
  16.         /**种方法,通过配置文件来实现  begin
  17.         ApplicationContext ctx = new ClassPathXmlApplicationContext(    “META-INF/WebServiceClient.xml”);
  18.         
  19.         HelloWorld client = (HelloWorld) ctx.getBean(“client”);
  20.         String result = client.sayHi(“Roamer”);
  21.         System.out.println(result);
  22.         
  23.         boolean logonResult = client.userLogon(“roamer”, “passwd”);
  24.         System.out.println(logonResult);
  25.         
  26.         logonResult = client.userLogon(“admin”, “passwd”);
  27.         System.out.println(logonResult);
  28.         */
  29.         JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
  30.         factory.setAddress(“http://localhost:8080/SampleWebService/webservice/HelloWorld”);
  31.         factory.setServiceClass(HelloWorld.class);
  32.         factory.getInInterceptors().add(new LoggingInInterceptor());
  33.         HelloWorld helloWorld = (HelloWorld) factory.create();
  34.         boolean msg = helloWorld.userLogon(“admin”,“World”);
  35.         System.out.println(msg);
  36.     }
  37. }
  1. /**
  2. *
  3. */
  4. package com.alcor.jws.test;
  5. import org.apache.cxf.interceptor.LoggingInInterceptor;
  6. import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
  7. /**
  8. * @author 徐泽宇(roamer)
  9. *
  10. * 2010-7-10
  11. */
  12. public class Client {
  13. private Client() {
  14. }
  15. public static void main(String args[]) throws Exception {
  16. /**种方法,通过配置文件来实现 begin
  17. ApplicationContext ctx = new ClassPathXmlApplicationContext( “META-INF/WebServiceClient.xml”);
  18. HelloWorld client = (HelloWorld) ctx.getBean(“client”);
  19. String result = client.sayHi(“Roamer”);
  20. System.out.println(result);
  21. boolean logonResult = client.userLogon(“roamer”, “passwd”);
  22. System.out.println(logonResult);
  23. logonResult = client.userLogon(“admin”, “passwd”);
  24. System.out.println(logonResult);
  25. */
  26. JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
  27. factory.setAddress(“http://localhost:8080/SampleWebService/webservice/HelloWorld”);
  28. factory.setServiceClass(HelloWorld.class);
  29. factory.getInInterceptors().add(new LoggingInInterceptor());
  30. HelloWorld helloWorld = (HelloWorld) factory.create();
  31. boolean msg = helloWorld.userLogon(“admin”,“World”);
  32. System.out.println(msg);
  33. }
  34. }

2。iphone 客户端的编程

  • LogonViewController.h
[cpp] view plain copy

  1. // 
  2. //  LogonViewController.h 
  3. //  IManager 
  4. // 
  5. //  Created by remote roamer on 11-11-22. 
  6. //  Copyright (c) 2011年 __MyCompanyName__. All rights reserved. 
  7. // 
  8. #import <UIKit/UIKit.h> 
  9. @interface LogonViewController : UIViewController<NSXMLParserDelegate>
  10. {
  11.     IBOutlet UITextField * userNameTextField;
  12.     IBOutlet UITextField * userPasswordTextField;
  13.     IBOutlet UIButton    * userLogonButton;
  14.     IBOutlet UITextField * webServiceURL;
  15.     NSXMLParser *xmlParser;
  16.     BOOL logonResult;
  17.     NSMutableString *soapResults;
  18. }
  19. @property(nonatomic,retain) IBOutlet UITextField * userNameTextField;
  20. @property(nonatomic,retain) IBOutlet UITextField * userPasswordTextField;
  21. @property(nonatomic,retain) IBOutlet UIButton    * userLogonButton;
  22. @property(nonatomic,retain) IBOutlet UITextField * webServiceURL;
  23. @property(nonatomic, retain) NSXMLParser *xmlParser;
  24. @property(nonatomic,retain) NSMutableString * soapResults;
  25. -(IBAction) logonButtonClick:(id)sender;
  26. @end
  1. //
  2. // LogonViewController.h
  3. // IManager
  4. //
  5. // Created by remote roamer on 11-11-22.
  6. // Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
  7. //
  8. #import <UIKit/UIKit.h>
  9. @interface LogonViewController : UIViewController<NSXMLParserDelegate>
  10. {
  11. IBOutlet UITextField * userNameTextField;
  12. IBOutlet UITextField * userPasswordTextField;
  13. IBOutlet UIButton * userLogonButton;
  14. IBOutlet UITextField * webServiceURL;
  15. NSXMLParser *xmlParser;
  16. BOOL logonResult;
  17. NSMutableString *soapResults;
  18. }
  19. @property(nonatomic,retain) IBOutlet UITextField * userNameTextField;
  20. @property(nonatomic,retain) IBOutlet UITextField * userPasswordTextField;
  21. @property(nonatomic,retain) IBOutlet UIButton * userLogonButton;
  22. @property(nonatomic,retain) IBOutlet UITextField * webServiceURL;
  23. @property(nonatomic, retain) NSXMLParser *xmlParser;
  24. @property(nonatomic,retain) NSMutableString * soapResults;
  25. -(IBAction) logonButtonClick:(id)sender;
  26. @end
  • LogonViewController.m
    [cpp] view plain copy

    1. // 
    2. //  LogonViewController.m 
    3. //  IManager 
    4. // 
    5. //  Created by remote roamer on 11-11-22. 
    6. //  Copyright (c) 2011年 __MyCompanyName__. All rights reserved. 
    7. // 
    8. #import “LogonViewController.h” 
    9. @implementation LogonViewController
    10. @synthesize userNameTextField;
    11. @synthesize userPasswordTextField;
    12. @synthesize userLogonButton;
    13. @synthesize webServiceURL;
    14. @synthesize xmlParser;
    15. @synthesize soapResults;
    16. -(IBAction) logonButtonClick:(id)sender
    17. {
    18.     logonResult = false;
    19.     NSString *soapMessage = [NSString stringWithFormat:
    20.                              @“<?xml version=\”1.0\” encoding=\”utf-8\”?>\n”
    21.                              “<soap:Envelope xmlns:xsi=\”http://www.w3.org/2001/XMLSchema-instance\” xmlns:xsd=\”http://www.w3.org/2001/XMLSchema\” xmlns:soap=\”http://schemas.xmlsoap.org/soap/envelope/\”>”
    22.                              “<soap:Body>\n”
    23.                              “<ns1:userLogon xmlns:ns1=\”http://localhost:8080/SampleWebService/webservice/HelloWorld/\”>”
    24.                              “<arg0>%@</arg0>”
    25.                              “<arg1>%@</arg1>”
    26.                              “</ns1:userLogon>”
    27.                              “</soap:Body>\n”
    28.                              “</soap:Envelope>”,self.userNameTextField.text,self.userPasswordTextField.text];
    29.     NSLog(@“调用webserivce的字符串是:%@”,soapMessage);
    30.     //请求发送到的路径 
    31.     NSString *msgLength = [NSString stringWithFormat:@“%d”, [soapMessage length]];
    32.     NSURL *url = [NSURL URLWithString:@“http://localhost:8080/SampleWebService/webservice/HelloWorld/”];
    33.     NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    34.      //以下对请求信息添加属性前四句是必有的, 
    35.     [urlRequest addValue: @“text/xml; charset=utf-8” forHTTPHeaderField:@“Content-Type”];
    36.     [urlRequest addValue: @“http://localhost:8080/SampleWebService/webservice/HelloWorld” forHTTPHeaderField:@“SOAPAction”];
    37.     [urlRequest addValue: msgLength forHTTPHeaderField:@“Content-Length”];
    38.     [urlRequest setHTTPMethod:@“POST”];
    39.     [urlRequest setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
    40.     //请求 
    41.     NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
    42.     theConnection = nil;
    43. }
    44. //如果调用有错误,则出现此信息 
    45. -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    46. {
    47.     NSLog(@“ERROR with theConenction”);
    48.     UIAlertView * alert =
    49.         [[UIAlertView alloc]
    50.              initWithTitle:@“提示”
    51.              message:[error description]
    52.              delegate:self
    53.              cancelButtonTitle:nil
    54.              otherButtonTitles:@“OK”, nil];
    55.     [alert show];
    56. }
    57. //调用成功,获得soap信息 
    58. -(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *)responseData
    59. {
    60.     NSString * returnSoapXML = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
    61.     NSLog(@“返回的soap信息是:%@”,returnSoapXML);
    62.     //开始解析xml 
    63.     xmlParser = [[NSXMLParser alloc] initWithData: responseData];
    64.     [xmlParser setDelegate:self];
    65.     [xmlParser setShouldResolveExternalEntities: YES];
    66.     [xmlParser parse];
    67.     if(logonResult){
    68.         UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@“登录成功” message:returnSoapXML delegate:nil cancelButtonTitle:@“ok” otherButtonTitles: nil];
    69.         [alert show];
    70.     }
    71. }
    72. //如果soap的返回字符串比较多。需要实现以下这个方法,配合 didReceiveData 方法来正确的接受到所有的soap字符串 
    73. //原因是:如果soap的返回字符串比较多。didReceiveData 这个方法会多次被调用。如果把soap解析的功能直接写在didReceiveData这个方法里面。会出现错误。这个时候,就需要 和connectionDidFinishLoading 联用。实现思路是:定义一个类变量NSMutableString * returnSoapXML;用于存放返回的soap字符串。 
    74. //一旦有返回内容,获得soap信息,追加到结果字符串中 
    75. //-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *)responseData 
    76. //{ 
    77. //    [returnSoapXML appendString:[[NSMutableString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]]; 
    78. //} 
    79. //*后在connectionDidFinishLoading 方法中实现,对完整的soap字符串的业务处理 
    80. //数据全部接受成功以后调用 
    81. /*
    82. – (void)connectionDidFinishLoading:(NSURLConnection *)connection
    83. {
    84.     NSLog(@”从远程ws中调用获得客户简单信息的调用成功!”);    
    85.     NSLog(@”返回的soap信息是:%@”,returnSoapXML);
    86.     //从soap 信息中解析出CusotmerInfo对象数组,并且保存到数据库中
    87.     NSLog(@”开始保存ws返回的内容到本地数据库”);
    88.     [[[SoapRtnJsonParser alloc] init] parse2CustomersInfo:[returnSoapXML dataUsingEncoding:NSUTF8StringEncoding]];
    89. }
    90. */
    91. -(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
    92. {
    93.     NSLog(@“返回的soap内容中,return值是: %@”,string);
    94.     if ([string isEqualToString:@“true”])
    95.     {
    96.         logonResult = YES;
    97.     }else{
    98.         logonResult = NO;
    99.     }
    100. }
    101. – (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    102. {
    103.     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    104.     if (self) {
    105.         // Custom initialization 
    106.     }
    107.     return self;
    108. }
    109. – (void)didReceiveMemoryWarning
    110. {
    111.     // Releases the view if it doesn’t have a superview. 
    112.     [super didReceiveMemoryWarning];
    113.     // Release any cached data, images, etc that aren’t in use. 
    114. }
    115. #pragma mark – View lifecycle 
    116. – (void)viewDidLoad
    117. {
    118.     [super viewDidLoad];
    119.     // Do any additional setup after loading the view from its nib. 
    120. }
    121. – (void)viewDidUnload
    122. {
    123.     [super viewDidUnload];
    124.     // Release any retained subviews of the main view. 
    125.     // e.g. self.myOutlet = nil; 
    126. }
    127. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    128. {
    129.     // Return YES for supported orientations 
    130.     return (interfaceOrientation == UIInterfaceOrientationPortrait);
    131. }
    132. @end
    1. //
    2. // LogonViewController.m
    3. // IManager
    4. //
    5. // Created by remote roamer on 11-11-22.
    6. // Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
    7. //
    8. #import “LogonViewController.h”
    9. @implementation LogonViewController
    10. @synthesize userNameTextField;
    11. @synthesize userPasswordTextField;
    12. @synthesize userLogonButton;
    13. @synthesize webServiceURL;
    14. @synthesize xmlParser;
    15. @synthesize soapResults;
    16. -(IBAction) logonButtonClick:(id)sender
    17. {
    18. logonResult = false;
    19. NSString *soapMessage = [NSString stringWithFormat:
    20. @“<?xml version=\”1.0\” encoding=\”utf-8\”?>\n”
    21. “<soap:Envelope xmlns:xsi=\”http://www.w3.org/2001/XMLSchema-instance\” xmlns:xsd=\”http://www.w3.org/2001/XMLSchema\” xmlns:soap=\”http://schemas.xmlsoap.org/soap/envelope/\”>”
    22. “<soap:Body>\n”
    23. “<ns1:userLogon xmlns:ns1=\”http://localhost:8080/SampleWebService/webservice/HelloWorld/\”>”
    24. “<arg0>%@</arg0>”
    25. “<arg1>%@</arg1>”
    26. “</ns1:userLogon>”
    27. “</soap:Body>\n”
    28. “</soap:Envelope>”,self.userNameTextField.text,self.userPasswordTextField.text];
    29. NSLog(@“调用webserivce的字符串是:%@”,soapMessage);
    30. //请求发送到的路径
    31. NSString *msgLength = [NSString stringWithFormat:@“%d”, [soapMessage length]];
    32. NSURL *url = [NSURL URLWithString:@“http://localhost:8080/SampleWebService/webservice/HelloWorld/”];
    33. NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    34. //以下对请求信息添加属性前四句是必有的,
    35. [urlRequest addValue: @“text/xml; charset=utf-8” forHTTPHeaderField:@“Content-Type”];
    36. [urlRequest addValue: @“http://localhost:8080/SampleWebService/webservice/HelloWorld” forHTTPHeaderField:@“SOAPAction”];
    37. [urlRequest addValue: msgLength forHTTPHeaderField:@“Content-Length”];
    38. [urlRequest setHTTPMethod:@“POST”];
    39. [urlRequest setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
    40. //请求
    41. NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
    42. theConnection = nil;
    43. }
    44. //如果调用有错误,则出现此信息
    45. -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    46. {
    47. NSLog(@“ERROR with theConenction”);
    48. UIAlertView * alert =
    49. [[UIAlertView alloc]
    50. initWithTitle:@“提示”
    51. message:[error description]
    52. delegate:self
    53. cancelButtonTitle:nil
    54. otherButtonTitles:@“OK”, nil];
    55. [alert show];
    56. }
    57. //调用成功,获得soap信息
    58. -(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *)responseData
    59. {
    60. NSString * returnSoapXML = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
    61. NSLog(@“返回的soap信息是:%@”,returnSoapXML);
    62. //开始解析xml
    63. xmlParser = [[NSXMLParser alloc] initWithData: responseData];
    64. [xmlParser setDelegate:self];
    65. [xmlParser setShouldResolveExternalEntities: YES];
    66. [xmlParser parse];
    67. if(logonResult){
    68. UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@“登录成功” message:returnSoapXML delegate:nil cancelButtonTitle:@“ok” otherButtonTitles: nil];
    69. [alert show];
    70. }
    71. }
    72. //如果soap的返回字符串比较多。需要实现以下这个方法,配合 didReceiveData 方法来正确的接受到所有的soap字符串
    73. //原因是:如果soap的返回字符串比较多。didReceiveData 这个方法会多次被调用。如果把soap解析的功能直接写在didReceiveData这个方法里面。会出现错误。这个时候,就需要 和connectionDidFinishLoading 联用。实现思路是:定义一个类变量NSMutableString * returnSoapXML;用于存放返回的soap字符串。
    74. //一旦有返回内容,获得soap信息,追加到结果字符串中
    75. //-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *)responseData
    76. //{
    77. // [returnSoapXML appendString:[[NSMutableString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]];
    78. //}
    79. //*后在connectionDidFinishLoading 方法中实现,对完整的soap字符串的业务处理
    80. //数据全部接受成功以后调用
    81. /*
    82. – (void)connectionDidFinishLoading:(NSURLConnection *)connection
    83. {
    84. NSLog(@”从远程ws中调用获得客户简单信息的调用成功!”);
    85. NSLog(@”返回的soap信息是:%@”,returnSoapXML);
    86. //从soap 信息中解析出CusotmerInfo对象数组,并且保存到数据库中
    87. NSLog(@”开始保存ws返回的内容到本地数据库”);
    88. [[[SoapRtnJsonParser alloc] init] parse2CustomersInfo:[returnSoapXML dataUsingEncoding:NSUTF8StringEncoding]];
    89. }
    90. */
    91. -(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
    92. {
    93. NSLog(@“返回的soap内容中,return值是: %@”,string);
    94. if ([string isEqualToString:@“true”])
    95. {
    96. logonResult = YES;
    97. }else{
    98. logonResult = NO;
    99. }
    100. }
    101. – (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    102. {
    103. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    104. if (self) {
    105. // Custom initialization
    106. }
    107. return self;
    108. }
    109. – (void)didReceiveMemoryWarning
    110. {
    111. // Releases the view if it doesn’t have a superview.
    112. [super didReceiveMemoryWarning];
    113. // Release any cached data, images, etc that aren’t in use.
    114. }
    115. #pragma mark – View lifecycle
    116. – (void)viewDidLoad
    117. {
    118. [super viewDidLoad];
    119. // Do any additional setup after loading the view from its nib.
    120. }
    121. – (void)viewDidUnload
    122. {
    123. [super viewDidUnload];
    124. // Release any retained subviews of the main view.
    125. // e.g. self.myOutlet = nil;
    126. }
    127. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    128. {
    129. // Return YES for supported orientations
    130. return (interfaceOrientation == UIInterfaceOrientationPortrait);
    131. }
    132. @end

其中要注意的几点。

  • [cpp] view plain copy

    1. <ns1:userLogon xmlns:ns1=\“http://localhost:8080/SampleWebService/webservice/HelloWorld/\”>”
    2.                              “<arg0>%@</arg0>”
    3.                              “<arg1>%@</arg1>”
    4.                              “</ns1:userLogon>”
    1. <ns1:userLogon xmlns:ns1=\“http://localhost:8080/SampleWebService/webservice/HelloWorld/\”>”
    2. “<arg0>%@</arg0>”
    3. “<arg1>%@</arg1>”
    4. “</ns1:userLogon>”

    中web service 中的 userLogon方法的调用 要用ns1来引用。

  • 传递的参数 不能和 webservice的变量名来写。而只能写成 arg0 和 arg1 这种方式。我查阅其他网上资料,都是写成<username>和<userpasswd>这种element的形式。但是在我的这个演示中,如果写成这种方式。后台会无法获得传入的变量。而用arg0 这种方式是可以传入。我不清楚是否是和 java cxf的webservice搭建环境和版本有关。

注意:如果后台的WebService是.net开发的,调用的程序略有不同:

例如 后台的ws服务链接是 :http://10.100.111.231:9000/MobileService.asmx

那么访问这个url,会返回如下内容:

这个页面提示了SOAP 1.1 和 SOAP 1.2 的调用的内容

[html] view plain copy

  1. SOAP 1.1
  2. The following is a sample SOAP 1.1 request and response. The placeholders shown need to be replaced with actual values.
  3.  POST /MobileService.asmx HTTP/1.1
  4. Host: 10.100.111.231
  5. Content-Type: text/xml; charset=utf-8
  6. Content-Length: length
  7. SOAPAction: “http://tempuri.org/Logon”
  8. <?xml version=“1.0” encoding=“utf-8”?>
  9. <soap:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap=“http://schemas.xmlsoap.org/soap/envelope/”>
  10.   <soap:Body>
  11.     <Logon xmlns=“http://tempuri.org/”>
  12.       <userName>string</userName>
  13.       <password>string</password>
  14.     </Logon>
  15.   </soap:Body>
  16. </soap:Envelope>
  17. HTTP/1.1 200 OK
  18. Content-Type: text/xml; charset=utf-8
  19. Content-Length: length
  20. <?xml version=“1.0” encoding=“utf-8”?>
  21. <soap:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap=“http://schemas.xmlsoap.org/soap/envelope/”>
  22.   <soap:Body>
  23.     <LogonResponse xmlns=“http://tempuri.org/”>
  24.       <LogonResult>string</LogonResult>
  25.     </LogonResponse>
  26.   </soap:Body>
  27. </soap:Envelope>
  28. SOAP 1.2
  29. The following is a sample SOAP 1.2 request and response. The placeholders shown need to be replaced with actual values.
  30. POST /MobileService.asmx HTTP/1.1
  31. Host: 10.100.111.231
  32. Content-Type: application/soap+xml; charset=utf-8
  33. Content-Length: length
  34. <?xml version=“1.0” encoding=“utf-8”?>
  35. <soap12:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap12=“http://www.w3.org/2003/05/soap-envelope”>
  36.   <soap12:Body>
  37.     <Logon xmlns=“http://tempuri.org/”>
  38.       <userName>string</userName>
  39.       <password>string</password>
  40.     </Logon>
  41.   </soap12:Body>
  42. </soap12:Envelope>
  43. HTTP/1.1 200 OK
  44. Content-Type: application/soap+xml; charset=utf-8
  45. Content-Length: length
  46. <?xml version=“1.0” encoding=“utf-8”?>
  47. <soap12:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap12=“http://www.w3.org/2003/05/soap-envelope”>
  48.   <soap12:Body>
  49.     <LogonResponse xmlns=“http://tempuri.org/”>
  50.       <LogonResult>string</LogonResult>
  51.     </LogonResponse>
  52.   </soap12:Body>
  53. </soap12:Envelope>
  1. SOAP 1.1
  2. The following is a sample SOAP 1.1 request and response. The placeholders shown need to be replaced with actual values.
  3.  POST /MobileService.asmx HTTP/1.1
  4. Host: 10.100.111.231
  5. Content-Type: text/xml; charset=utf-8
  6. Content-Length: length
  7. SOAPAction: “http://tempuri.org/Logon”
  8. <?xml version=”1.0″ encoding=”utf-8″?>
  9. <soap:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap=“http://schemas.xmlsoap.org/soap/envelope/”>
  10. <soap:Body>
  11. <Logon xmlns=“http://tempuri.org/”>
  12. <userName>string</userName>
  13. <password>string</password>
  14. </Logon>
  15. </soap:Body>
  16. </soap:Envelope>
  17. HTTP/1.1 200 OK
  18. Content-Type: text/xml; charset=utf-8
  19. Content-Length: length
  20. <?xml version=”1.0″ encoding=”utf-8″?>
  21. <soap:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap=“http://schemas.xmlsoap.org/soap/envelope/”>
  22. <soap:Body>
  23. <LogonResponse xmlns=“http://tempuri.org/”>
  24. <LogonResult>string</LogonResult>
  25. </LogonResponse>
  26. </soap:Body>
  27. </soap:Envelope>
  28. SOAP 1.2
  29. The following is a sample SOAP 1.2 request and response. The placeholders shown need to be replaced with actual values.
  30. POST /MobileService.asmx HTTP/1.1
  31. Host: 10.100.111.231
  32. Content-Type: application/soap+xml; charset=utf-8
  33. Content-Length: length
  34. <?xml version=”1.0″ encoding=”utf-8″?>
  35. <soap12:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap12=“http://www.w3.org/2003/05/soap-envelope”>
  36. <soap12:Body>
  37. <Logon xmlns=“http://tempuri.org/”>
  38. <userName>string</userName>
  39. <password>string</password>
  40. </Logon>
  41. </soap12:Body>
  42. </soap12:Envelope>
  43. HTTP/1.1 200 OK
  44. Content-Type: application/soap+xml; charset=utf-8
  45. Content-Length: length
  46. <?xml version=”1.0″ encoding=”utf-8″?>
  47. <soap12:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soap12=“http://www.w3.org/2003/05/soap-envelope”>
  48. <soap12:Body>
  49. <LogonResponse xmlns=“http://tempuri.org/”>
  50. <LogonResult>string</LogonResult>
  51. </LogonResponse>
  52. </soap12:Body>
  53. </soap12:Envelope>

 

那么,我们在objectiveC中代码应该写成

static NSString * wsURL = @”http://10.100.111.231:9000/MobileService.asmx”;

NSString *soapMessage = [NSString stringWithFormat:
@”<?xml version=\”1.0\” encoding=\”utf-8\”?>\n”
“<soap:Envelope xmlns:xsi=\”http://www.w3.org/2001/XMLSchema-instance\” xmlns:xsd=\”http://www.w3.org/2001/XMLSchema\” xmlns:soap=\”http://schemas.xmlsoap.org/soap/envelope/\”>”
“<soap:Body>\n”
“<Logon xmlns=\”http://tempuri.org/\”>”
“<userName>%@</userName>”
“<password>%@</password>”
“</Logon>”
“</soap:Body>\n”
“</soap:Envelope>”,self.userNameTextField.text,self.userPasswordTextField.text];

NSLog(@”调用webserivce的字符串是:%@”,soapMessage);
//请求发送到的路径
NSString *msgLength = [NSString stringWithFormat:@”%d”, [soapMessage length]];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@”%@”,wsURL]];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];

//以下对请求信息添加属性前四句是必有的,
[urlRequest addValue: @”text/xml; charset=utf-8″ forHTTPHeaderField:@”Content-Type”];
[urlRequest addValue:@”http://tempuri.org/Logon” forHTTPHeaderField:@”SOAPAction”];
NSLog(@”SOAPAction is %@ “,@”http://tempuri.org/Logon”);
[urlRequest addValue: msgLength forHTTPHeaderField:@”Content-Length”];
[urlRequest setHTTPMethod:@”POST”];
[urlRequest setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];

//请求
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
theConnection = nil;

注意红色的代码,这个http://tempurl.org/Logon 是要和 .net中的代码一致。否则无法访问。

这个字符串的内容可以从asmx返回帮助界面来获得。

IOS学习之ios开发之数据的持久化存储机制

IOS中数据的持久化保存这块内容,类似于Android中文件的几种常见的存储方式。
对于数据的持久化存储,ios中一般提供了4种不同的机制。
1.属性列表
2.对象归档
3.数据库存储(SQLite3)
4.苹果公司提供的持久性工具Core Data。

其实储存的形式无非就这么几种,而我们还必须要关心的是,这些文件会被放置在那个文件下,然后如何读取。
也就是说:IOS上数据存储,我们要了解的两点,数据存储格式(也就是存储机制),数据存储位置。
1》文件如何存储(如上面4点)
2》文件存储在哪里。
对于数据的操作,其实我们关心的是操作的速率。
就好比在Adnroid中偏好存储,数据库存储,io存储一样。
我大致问了我们公司新来的ios哥们,他说他们培训机构基本对数据操作这块就讲了属性列表和数据库,以及普通的文件存储(比如音视频图这些多媒体数据)。
我就只好先看看书了。

一:应用文件目录
首先我们来看了解下ios数据存储位置,因为只有知道位置路径我们才能去读取数据,而数据的持久化机制不过是针对操作速率来考虑的,
比如我们大致知道属性列表(既键值对形式)的存储熟虑应该高于数据库高于io文件流存储。
我们在选择用何种机制存储数据,主要也是看数据的形式。

一个ios应用安装后大致会有如下文件夹及其对应路径:

%title插图%num

在mac上看模拟器中应用路径:
/Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3

你在finder中的home下可能找不到Library这个目录,因为貌似是影藏起来了(我这机器上是,在终端可以看到)。
*后那一窜的类似序列号的东西就是ios自动给应用生成的一组应用唯一识别码*为了应用的home目录名。
其下面就是上图所示了。
书上对这些文件夹介绍:

Document:应用程序将其数据存储在这个文件夹下,基于NSUserDefaults的首选项的设置除外。

简单理解是,基本上我们要操作的一些数据都是存储在这个文件夹下面的

TIPS:这边提下一点,对于ios系统这么分配文件夹,是因为在设备进行同步时,ITunes有选择性的意识来备份文件。

比如我们可以猜到,tmp下的应该就不会备份了。

对于Document文件夹目录路径的获取,API提供了这么一种方法:

[cpp] view plain copy

  1. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  2.    NSString *docPath = [paths objectAtIndex:0];
  1. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  2. NSString *docPath = [paths objectAtIndex:0];

Library:基于NSUserDefault首选项设置存储在其下Preferences文件夹中,简单来说,这个文件夹一般你很少操作到。

书上对于这部分基本没介绍。估计对于初级部分是跳过了。

Tmp:应用临时存储文件,当不需要时,应用负责删除其下的文件数据。

该文件也提供了目录获取方法:

[cpp] view plain copy

  1. NSString *tmpDoc = NSTemporaryDirectory();
NSString *tmpDoc = NSTemporaryDirectory();

应用程序文件:这个基本没提到书上,但是我们大致可以猜测,这就是整个应用程序的程序文件夹吧。

好了,以上我们大致解决了我们提到的*个点,文件存储目录

二:数据存储机制

1.属性列表

这个其实我们早见过,plist就是,感觉用来存储键值对小数据是*合适,因为速率很高。

这个存储机制很简单,对于前面我们使用过了在plist文件来读取数据填充一些列表,只不过那会plist文件存储位置不同,

用的是Mainbundle什么的来返回文件夹,其实这边我也推测,上面提到有个应用程序文件夹,它下面的文件就是这么来读取的~(反正暂时不管他)

这边不过就是改变了存储位置,数据操作还是一样的

[cpp] view plain copy

  1. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  2. NSString *docPath = [paths objectAtIndex:0];
  3. NSString *myFile = [docPath stringByAppendingPathComponent:@“my.list”];
  4. //读取文件 
  5. NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];
  6. //操作完若修改了数据则,写入文件 
  7. [array writeToFile:myFile atomically:YES];
  1. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  2. NSString *docPath = [paths objectAtIndex:0];
  3. NSString *myFile = [docPath stringByAppendingPathComponent:@“my.list”];
  4. //读取文件
  5. NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];
  6. //操作完若修改了数据则,写入文件
  7. [array writeToFile:myFile atomically:YES];

2.对象归档

上面的属性列表存储机制,我们都知道,这个机制支持NSArray,NSDictionary,NSData,NSString,NSNumber,NSDate 等等

这些对象直接写入plist文件中。

那么对于一些复杂对象,我要保存整个这个对象数据呢?

反正我是这么觉得,这个机制很像java中的对象整体序列化。当然,这些数据在读取是就需要遵循一种墨守成规的协议了。

首先我们定义的对象类,必须实现NSCoding和NSCopying协议(额,网上说后面这个不实现也可以,我猜是他对象没有copy操作,因此没出错)书本上反正是实现了这两个协议
然后归档中用到的操作类
NSKeyedArchiver
这边我们定义一个对象,h文件中定义两属性,申明要实现的NSCoding和NSCopying协议
实现文件

[cpp] view plain copy

  1. // 
  2. //  TestObj.m 
  3. //  DataStorageTest 
  4. // 
  5. //  Created by Nono on 12-5-12. 
  6. //  Copyright (c) 2012年 NonoWithLilith. All rights reserved. 
  7. // 
  8. #import “TestObj.h” 
  9. @implementation TestObj
  10. @synthesize stringA = stringA;
  11. @synthesize stringB = stringB;
  12. #pragma mark – 
  13. #pragma NSCoding协议实现实现 
  14. – (void)encodeWithCoder:(NSCoder *)aCoder
  15. {   //encoder 
  16.     [aCoder encodeObject:stringAforKey:@“1”];
  17.     [aCoder encodeObject:stringBforKey:@“2”];
  18. }
  19. – (id)initWithCoder:(NSCoder *)aDecoder
  20. {
  21.     //decoder 
  22.     if (self = [superinit]) {
  23.         stringA = [[aDecoder decodeObjectForKey:@“1”] retain];
  24.         stringB = [[aDecoder decodeObjectForKey:@“2”] retain];
  25.     }
  26.     returnself;
  27. }
  28. #pragma NSCopying协议实现 
  29. – (id)copyWithZone:(NSZone *)zone
  30. {
  31.     TestObj *copy = [[[selfclass] allocWithZone:zone] init];
  32.     copy.stringA = [[self.stringAcopyWithZone:zone] autorelease];
  33.     copy.stringB = [[self.stringBcopyWithZone:zone] autorelease];
  34.     return copy;
  35. }
  36. @end
  1. //
  2. // TestObj.m
  3. // DataStorageTest
  4. //
  5. // Created by Nono on 12-5-12.
  6. // Copyright (c) 2012年 NonoWithLilith. All rights reserved.
  7. //
  8. #import “TestObj.h”
  9. @implementation TestObj
  10. @synthesize stringA = stringA;
  11. @synthesize stringB = stringB;
  12. #pragma mark –
  13. #pragma NSCoding协议实现实现
  14. – (void)encodeWithCoder:(NSCoder *)aCoder
  15. { //encoder
  16. [aCoder encodeObject:stringAforKey:@“1”];
  17. [aCoder encodeObject:stringBforKey:@“2”];
  18. }
  19. – (id)initWithCoder:(NSCoder *)aDecoder
  20. {
  21. //decoder
  22. if (self = [superinit]) {
  23. stringA = [[aDecoder decodeObjectForKey:@“1”] retain];
  24. stringB = [[aDecoder decodeObjectForKey:@“2”] retain];
  25. }
  26. returnself;
  27. }
  28. #pragma NSCopying协议实现
  29. – (id)copyWithZone:(NSZone *)zone
  30. {
  31. TestObj *copy = [[[selfclass] allocWithZone:zone] init];
  32. copy.stringA = [[self.stringAcopyWithZone:zone] autorelease];
  33. copy.stringB = [[self.stringBcopyWithZone:zone] autorelease];
  34. return copy;
  35. }
  36. @end

然后是对对象归档的读取和写入

[cpp] view plain copy

  1. //读取归档文件 
  2.    NSData *data = [[NSMutableDataalloc] initWithContentsOfFile:myFile];
  3.    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiveralloc] initForReadingWithData:data];
  4.    TestObj * test = [unarchiver decodeObjectForKey:@“data”];
  5.    [unarchiver finishDecoding];
  6.    [data release];
  7.    [unarchiver release];
  8.    //写入归档文件 
  9.    NSMutableData *data1 = [[NSMutableDataalloc] init];
  10.    NSKeyedArchiver *archiver = [[NSKeyedArchiveralloc] initForWritingWithMutableData:data1];
  11.    [archiver encodeObject:test forKey:@“data”];
  12.    [archiver finishEncoding];
  13.    [data writeToFile:myFile atomically:YES];
  14.    [data1 release];
  15.    [archiver release];
  16.    [test release];
  1. //读取归档文件
  2. NSData *data = [[NSMutableDataalloc] initWithContentsOfFile:myFile];
  3. NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiveralloc] initForReadingWithData:data];
  4. TestObj * test = [unarchiver decodeObjectForKey:@“data”];
  5. [unarchiver finishDecoding];
  6. [data release];
  7. [unarchiver release];
  8. //写入归档文件
  9. NSMutableData *data1 = [[NSMutableDataalloc] init];
  10. NSKeyedArchiver *archiver = [[NSKeyedArchiveralloc] initForWritingWithMutableData:data1];
  11. [archiver encodeObject:test forKey:@“data”];
  12. [archiver finishEncoding];
  13. [data writeToFile:myFile atomically:YES];
  14. [data1 release];
  15. [archiver release];
  16. [test release];

但是问了下新同事,据说这个用到也是蛮少,至少他目前。
但是,我看了下,觉得这个和Android 中Parcelable
太尼玛像似了

三.数据库存储
和Android一样,ios中也是用了SQLite3这种嵌入式数据库。

这个网上例子是很多了。我这边就大致看下了数据库的打开,

数据库表创建,查询,插入

[cpp] view plain copy

  1.  //数据库操作 
  2.  sqlite3 *database;
  3. // const NSString * dbname = @”mydb” 
  4.  int result;
  5.  //打开一个指定路径的现有的数据库,如果没有则会新建一个db库 
  6.  result =  sqlite3_open([myFile UTF8String], &database);
  7.  if (result != SQLITE_OK) {
  8.      sqlite3_close(database);
  9.  }
  10.  //创建一个db表 
  11.  char *errorMsg;
  12.  NSString *sql_create_table = @“CREATE TABLE IF NOT EXISTS NONOTABLE 省略~~~~~~~~~~~~~”;
  13.  int result1 ;
  14.  //sqlite_exec用了针对sqlite3运行任何不要返回数据的命令,它用于执行更新,插入和删除。简单来说,这个方法执行的都是一些无需返回数据(虽然我们可能获取一个状态值。)。 
  15.  result1 = sqlite3_exec(database, [sql_create_table UTF8String], NULL, NULL, &errorMsg);
  16.  //检索查询操作 
  17.  int result2 ;
  18.  sqlite3_stmt *statment;
  19.  NSString *sql_selected = @“查询语句”;
  20. result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil);
  21.  if(result2 == SQLITE_OK){
  22.      //单步操作 
  23.      while (sqlite3_step(statment) == SQLITE_ROW) {
  24.          int row = sqlite3_column_int(statment, 0);
  25.          char * rpwData = sqlite3_column_text(statment, 1);
  26.      }
  27.      sqlite3_finalize(statment);
  28.  }
  29.  //绑定变量,既就是插入操作的一种变种,比如我么那上面提到sqlite_exec可以执行插入操作,插入内容直接是写在sql字窜里,但是考虑到字窜涉及到无效的符号以及会一些严重的注入漏洞(比如以前听过的引号符号)。 
  30.  NSString *sql_bind = @“insert into foo value(?,?)”;
  31.  result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil);
  32.  if(result2 == SQLITE_OK){
  33.      sqlite3_bind_int(statment, 1, 235);
  34.      sqlite3_bind_text(statment, 2, “test”, -1, nil);
  35.            sqlite3_finalize(statment);
  36.  }
  37.  if (sqlite3_step(statment) != SQLITE_DONE)
  38.      NSLog(@“error”);
  39.  sqlite3_finalize(statment);
  40.  sqlite3_close(database);
  1. //数据库操作
  2. sqlite3 *database;
  3. // const NSString * dbname = @”mydb”
  4. int result;
  5. //打开一个指定路径的现有的数据库,如果没有则会新建一个db库
  6. result = sqlite3_open([myFile UTF8String], &database);
  7. if (result != SQLITE_OK) {
  8. sqlite3_close(database);
  9. }
  10. //创建一个db表
  11. char *errorMsg;
  12. NSString *sql_create_table = @“CREATE TABLE IF NOT EXISTS NONOTABLE 省略~~~~~~~~~~~~~”;
  13. int result1 ;
  14. //sqlite_exec用了针对sqlite3运行任何不要返回数据的命令,它用于执行更新,插入和删除。简单来说,这个方法执行的都是一些无需返回数据(虽然我们可能获取一个状态值。)。
  15. result1 = sqlite3_exec(database, [sql_create_table UTF8String], NULL, NULL, &errorMsg);
  16. //检索查询操作
  17. int result2 ;
  18. sqlite3_stmt *statment;
  19. NSString *sql_selected = @“查询语句”;
  20. result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil);
  21. if(result2 == SQLITE_OK){
  22. //单步操作
  23. while (sqlite3_step(statment) == SQLITE_ROW) {
  24. int row = sqlite3_column_int(statment, 0);
  25. char * rpwData = sqlite3_column_text(statment, 1);
  26. }
  27. sqlite3_finalize(statment);
  28. }
  29. //绑定变量,既就是插入操作的一种变种,比如我么那上面提到sqlite_exec可以执行插入操作,插入内容直接是写在sql字窜里,但是考虑到字窜涉及到无效的符号以及会一些严重的注入漏洞(比如以前听过的引号符号)。
  30. NSString *sql_bind = @“insert into foo value(?,?)”;
  31. result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil);
  32. if(result2 == SQLITE_OK){
  33. sqlite3_bind_int(statment, 1, 235);
  34. sqlite3_bind_text(statment, 2, “test”, -1, nil);
  35. sqlite3_finalize(statment);
  36. }
  37. if (sqlite3_step(statment) != SQLITE_DONE)
  38. NSLog(@“error”);
  39. sqlite3_finalize(statment);
  40. sqlite3_close(database);

关于更多的,大伙可以自行百度,因为数据库的操作语法太怪异了,书上说是基本是基于c的,本人没学过c。看得有点心烦~

4。Core Data存储机制

大致浏览下基本感觉就是将对象归档搞成了可视化和简单化。

这块内容比较多。网上资料也挺丰富的。

暂时不做介绍了。

总结下:其实对于ios数据存储,*常用和主要要掌握的就是属性列表和数据库,因为两个是出镜率比较高的。

其他可能在数据存明显体现出储优势时,我们会去考虑用另外两种机制。

基础的来说,必须掌握属性列表和sqlite的操作存储。

2021年3月中国采购经理指数运行情况

国家统计局服务业调查中心中国物流与采购联合会   一、中国制造业采购经理指数运行情况   3月份,中国制造业采购经理指数(PMI)为51.9%,高于上月1.3个百分点,制造业景气回升。    从企业规模看,大、中、小型企业PMI分别为52.7%、51.6%和50.4%,较上月上升0.5、2.0和2.1个百分点,均高于临界点。   从分类指数看,在构成制造业PMI的5个分类指数中,生产指数、新订单指数、从业人员指数均高于临界点,供应商配送时间指数位于临界点,原材料库存指数低于临界点。   生产指数为53.9%,比上月上升2.0个百分点,高于临界点,表明制造业生产扩张步伐加快。   新订单指数为53.6%,比上月上升2.1个百分点,高于临界点,表明制造业市场需求增长较快。   原材料库存指数为48.4%,比上月回升0.7个百分点,低于临界点,表明制造业主要原材料库存量降幅收窄。   从业人员指数为50.1%,比上月上升2.0个百分点,高于临界点,表明制造业企业用工量较上月略有增加。   供应商配送时间指数为50.0%,比上月上升2.1个百分点,但位于临界点,表明制造业原材料供应商交货时间与上月相比变化不大。 表1  中国制造业PMI及构成指数(经季节调整)                                              单位:%   PMI   生产 新订单 原材料 库存 从业人员 供应商配送时间 2020年3月 52.0 54.1 52.0 49.0 50.9 48.2 2020年4月 50.8 53.7 50.2 48.2 50.2 50.1 2020年5月 50.6 53.2 50.9 47.3 49.4 50.5 2020年6月 50.9 53.9 51.4 47.6 49.1 50.5 2020年7月 51.1 54.0 51.7 47.9 49.3 50.4 2020年8月 51.0 53.5 52.0 47.3 49.4 50.4 2020年9月 51.5 54.0 52.8 48.5 49.6 50.7 2020年10月 51.4 53.9 52.8 48.0 49.3 50.6 2020年11月 52.1 54.7 53.9 48.6 49.5 50.1 2020年12月 51.9 54.2 53.6 48.6 49.6 49.9 2021年1月 51.3 53.5 52.3 49.0 48.4 48.8 2021年2月 50.6 51.9 51.5 47.7 48.1 47.9 2021年3月 51.9 53.9 53.6 48.4 50.1 50.0  表2  中国制造业PMI其他相关指标情况(经季节调整)                                              单位:%   新出口 订单 进口 采购量 主要原材料购进价格 出厂 价格 产成品 库存 在手 订单 生产经营活动预期 2020年3月 46.4 48.4 52.7 45.5 43.8 49.1 46.3 54.4 2020年4月 33.5 43.9 52.0 42.5 42.2 49.3 43.6 54.0 2020年5月 35.3 45.3 50.8 51.6 48.7 47.3 44.1 57.9 2020年6月 42.6 47.0 51.8 56.8 52.4 46.8 44.8 57.5 2020年7月 48.4 49.1 52.4 58.1 52.2 47.6 45.6 57.8 2020年8月 49.1 49.0 51.7 58.3 53.2 47.1 46.0 58.6 2020年9月 50.8 50.4 53.6 58.5 52.5 48.4 46.1 58.7 2020年10月 51.0 50.8 53.1 58.8 53.2 44.9 47.2 59.3 2020年11月 51.5 50.9 53.7 62.6 56.5 45.7 46.7 60.1 2020年12月 51.3 50.4 53.2 68.0 58.9 46.2 47.1 59.8 2021年1月 50.2 49.8 52.0 67.1 57.2 49.0 47.3 57.9 2021年2月 48.8 49.6 51.6 66.7 58.5 48.0 46.1 59.2 2021年3月 51.2 51.1 53.1 69.4 59.8 46.7 46.6 58.5    二、中国非制造业采购经理指数运行情况   3月份,非制造业商务活动指数为56.3%,较上月上升4.9个百分点,表明非制造业扩张有所加快。    分行业看,建筑业商务活动指数为62.3%,高于上月7.6个百分点。服务业商务活动指数为55.2%,高于上月4.4个百分点。从行业情况看,调查的21个服务业行业商务活动指数均高于临界点,其中铁路运输、航空运输、电信广播电视卫星传输服务、互联网软件及信息技术服务、货币金融服务、保险等行业商务活动指数位于60.0%以上高位景气区间。     新订单指数为55.9%,比上月上升7.0个百分点,表明非制造业市场需求较上月增加。分行业看,建筑业新订单指数为59.0%,比上月上升5.6个百分点;服务业新订单指数为55.4%,比上月上升7.3个百分点。   投入品价格指数为56.2%,比上月上升1.5个百分点,表明非制造业企业用于经营活动的投入品价格总体上涨。分行业看,建筑业投入品价格指数为63.6%,比上月上升3.4个百分点;服务业投入品价格指数为54.8%,比上月上升1.1个百分点。   销售价格指数为52.2%,比上月上升2.1个百分点,表明非制造业销售价格总体上涨幅度有所加大。分行业看,建筑业销售价格指数为55.0%,比上月上升2.5个百分点;服务业销售价格指数为51.7%,比上月上升2.0个百分点。   从业人员指数为49.7%,比上月回升1.3个百分点,表明非制造业用工景气度继续回升。分行业看,建筑业从业人员指数为55.0%,比上月上升0.8个百分点;服务业从业人员指数为48.8%,比上月回升1.4个百分点。   业务活动预期指数为63.7%,虽比上月回落0.3个百分点,但继续位于高位景气区间,表明非制造业企业对行业发展保持乐观。分行业看,建筑业业务活动预期指数为68.1%,比上月微落0.1个百分点;服务业业务活动预期指数为62.9%,比上月回落0.3个百分点。 表3  中国非制造业主要分类指数(经季节调整)                                             单位:%    商务活动 新订单 投入品 价格 销售价格 从业人员 业务活动 预期 2020年3月 52.3 49.2 49.4 46.1 47.7 57.3 2020年4月 53.2 52.1 49.0 45.4 48.6 60.1 2020年5月 53.6 52.6 52.0 48.6 48.5 63.9 2020年6月 54.4 52.7 52.9 49.5 48.7 60.3 2020年7月 54.2 51.5 53.0 50.1 48.1 62.2 2020年8月 55.2 52.3 51.9 50.1 48.3 62.1 2020年9月 55.9 54.0 50.6 48.9 49.1 63.0 2020年10月 56.2 53.0 50.9 49.4 49.4 62.9 2020年11月 56.4 52.8 52.7 51.0 48.9 61.2 2020年12月 55.7 51.9 54.3 52.3 48.7 60.6 2021年1月 52.4 48.7 54.5 51.4 47.8 55.1 2021年2月 51.4 48.9 54.7 50.1 48.4 64.0 2021年3月 56.3 55.9 56.2 52.2 49.7 63.7  表4  中国非制造业其他分类指数(经季节调整)                                              单位:%   新出口订单 在手订单 存货 供应商配送时间 2020年3月 38.6 43.0 46.1 46.4 2020年4月 35.5 43.4 47.0 51.0 2020年5月 41.3 44.3 47.8 52.9 2020年6月 43.3 44.8 48.0 52.1 2020年7月 44.5 44.9 48.1 51.9 2020年8月 45.1 44.6 48.5 52.4 2020年9月 49.1 46.3 48.5 52.2 2020年10月 47.0 44.9 48.7 52.3 2020年11月 49.0 45.2 48.8 51.8 2020年12月 47.5 44.7 47.0 51.2 2021年1月 48.0 44.0 47.4 49.8 2021年2月 45.7 44.0 45.9 49.8 2021年3月 50.3 45.9 48.2 51.8    三、中国综合PMI产出指数运行情况   3月份,综合PMI产出指数为55.3%,比上月上升3.7个百分点,表明我国企业生产经营活动总体加快。    附注   1.主要指标解释   采购经理指数(PMI),是通过对企业采购经理的月度调查结果统计汇总、编制而成的指数,它涵盖了企业采购、生产、流通等各个环节,包括制造业和非制造业领域,是国际上通用的监测宏观经济走势的先行性指数之一,具有较强的预测、预警作用。综合PMI产出指数是PMI指标体系中反映当期全行业(制造业和非制造业)产出变化情况的综合指数。PMI高于50%时,反映经济总体较上月扩张;低于50%,则反映经济总体较上月收缩。   2.调查范围   涉及《国民经济行业分类》(GB/T4754-2017)中制造业的31个行业大类,3000家调查样本;非制造业的43个行业大类,4200家调查样本。   3.调查方法   采购经理调查采用PPS(Probability Proportional to Size)抽样方法,以制造业或非制造业行业大类为层,行业样本量按其增加值占全部制造业或非制造业增加值的比重分配,层内样本使用与企业主营业务收入成比例的概率抽取。   本调查由国家统计局直属调查队具体组织实施,利用国家统计联网直报系统对企业采购经理进行月度问卷调查。   4.计算方法   (1)分类指数的计算方法。制造业采购经理调查指标体系包括生产、新订单、新出口订单、在手订单、产成品库存、采购量、进口、主要原材料购进价格、出厂价格、原材料库存、从业人员、供应商配送时间、生产经营活动预期等13个分类指数。非制造业采购经理调查指标体系包括商务活动、新订单、新出口订单、在手订单、存货、投入品价格、销售价格、从业人员、供应商配送时间、业务活动预期等10个分类指数。分类指数采用扩散指数计算方法,即正向回答的企业个数百分比加上回答不变的百分比的一半。由于非制造业没有合成指数,国际上通常用商务活动指数反映非制造业经济发展的总体变化情况。   (2)制造业PMI指数的计算方法。制造业PMI是由5个扩散指数(分类指数)加权计算而成。5个分类指数及其权数是依据其对经济的先行影响程度确定的。具体包括:新订单指数,权数为30%;生产指数,权数为25%;从业人员指数,权数为20%;供应商配送时间指数,权数为15%;原材料库存指数,权数为10%。其中,供应商配送时间指数为逆指数,在合成制造业PMI指数时进行反向运算。   (3)综合PMI产出指数的计算方法。综合PMI产出指数由制造业生产指数与非制造业商务活动指数加权求和而成,权数分别为制造业和非制造业占GDP的比重。   5.季节调整   采购经理调查是一项月度调查,受季节因素影响,数据波动较大。现发布的指数均为季节调整后的数据。 

Pandas常用功能笔记

Pandas常用功能笔记

pandas包导入
import pandas as pd
单个sheet处理
# 读取excel数据
file = ‘./aa.xlsx’
data_1 = pd.read_excel(file)
data_1.to_excel(file_save, index=False, header=False)

# 写入excel数据
file_save = ‘./bb.xlsx’
sample_1 = [(1, 2), (3, 4), (5, 6)]
data_2 = pd.Data_Frame(sample_1)
data_2.to_excel(file_save, index=False, header=[‘odd’, ‘even’])

sample_2 = {‘odd’: [1, 3, 5], ‘even’: [2, 4, 6]}
data_3 = pd.DataFrame(sample_2)
data_3.to_excel(file_save, index=False, header=True)
多个sheet处理
# 读取sheet数据
file = ‘./aa.xlsx’
data_1 = pd.read_excel(file, sheet=’data_1′)

# 写入sheet数据
f_save = pd.ExcelWriter(‘./bb.xlsx’)
sample_1 = [(1, 2), (3, 4), (5, 6)]
sheet_1 = pd.Data_Frame(sample_1)
sheet_1.to_excel(file_save, index=False, header=[‘odd’, ‘even’], sheet_name=’tuple_data’)

sample_2 = {‘odd’: [1, 3, 5], ‘even’: [2, 4, 6]}
sheet_2 = pd.DataFrame(sample_2)
sheet_2.to_excel(file_save, index=False, header=True, sheet_name=’dict_data’)
数据选择
file = r’./aa.xlsx’
df = pd.read_excel(file)
odd_data = df[‘odd’]
odd_list = df[‘odd’].values.to_list()

# 按名称选择
odd, even = df.loc([:, [‘odd’, ‘even’]])
odd_part, even_part = df.loc([0:2, [‘odd’, ‘even’]]) # 选取前两行

# 按列顺序选择
odd, even = df.iloc([:, [0, 1]])
odd_part, even_part = df.iloc([0:2, [0, 1]])
数据去重
file = r’./aa.xlsx’
df = pd.read_excel(file)
df.drop_duplicates(subset=[‘odd’,’even’],keep=’first’,inplace=True) # 在原数据上操作

# 不在原数据上操作,重新生成副本
new_data = drop_duplicates(subset=[‘odd’,’even’],keep=’first’,inplace=False)
遍历excel中的所有sheet
file = r’./aa.xlsx’
df = pd.ExcelFile(file)

for name in df.sheet_names:
sheet = pd.read_excel(file, sheet_name=name)
……

python实现固定尺寸图像拼接

python实现固定尺寸图像拼接

python实现固定尺寸图像拼接
讲解
1、代码效果:固定尺寸图像拼接
代码

import os
import cv2
import numpy as np

def joint(or_path, tar_path, size):
determination = tar_path
if not os.path.exists(determination):
os.makedirs(determination)

path = or_path
folders = os.listdir(path)
folders_name = []
folders.sort(key = lambda x: int(x.split(‘.’)[0]))
for folder in folders:
folders_name.append(path + “\\” + str(folder))
foldler_len = len(folders_name)
joint = []
for i in range(size):
joint.append(cv2.imread(folders_name[i]))
index = 0
for i in range(len(folders_name)):
if i < size:
continue
image = cv2.imread(folders_name[i])
joint[index] = np.hstack((joint[index], image))
index += 1
if index > size – 1:
index = 0
for i in range(1, len(joint)):
joint[0] = np.vstack((joint[0], joint[i]))
cv2.imwrite(tar_path + “/all.jpg”, joint[0])
print(joint[0].shape)

if __name__ == “__main__”:
joint(r””, r””, size)#原图像文件夹路径、新图像存放路径、单方向图像数

运行结果
运行前%title插图%num

运行后

%title插图%num

ArcPy基础之字典(二)

ArcPy基础之字典(二)

文章目录
一、编译环境和编译器
二、字典
1.定义字典,获取字典关键字、值、对象
2.字典的操作:增、删、改、查、复制和创建
一、编译环境和编译器
ArcGIS10.6:Python2.7.14 32-bit
编译器:Visual Studio Code

二、字典
1.定义字典,获取字典关键字、值、对象
代码如下(示例):

# encoding=utf-8

#定义字典{key:value}
dicA={“name”:”Xiao Ming”,”age”:14,”high”:167,”other”:””}
print(dicA)
print(len(dicA))#打印字典长度
#获取关键字、值、对象
print(dicA.keys())#打印字典关键字
print(type(dicA.keys()))#字典关键字类型,list

print(dicA.values())#打印值
print(type(dicA.values()))#字典值类型,list

print(dicA.items())#打印对象
print(type(dicA.items()))#字典对象类型,list
print(“———-0”)

#通过循环获取,以获取对象为例
for item in dicA.items():
print(item)

print(“———-1”)

#根据关键字获取值
print(dicA.get(“name”))#根据关键字”name”获取值Xiao Ming
dicA.get(“name”)#当获取的关键字在原字典中不存在时,get()会返回默认值,但不改变原字典。
print(dicA)

print(dicA.setdefault(‘name’))#根据关键字”name”获取名字Xiao Ming
dicA.setdefault(‘grades’)#当获取的关键字在原字典中不存在时,setdefaul()t会返回默认值None并更新字典
print(dicA)

打印结果:

{‘high’: 167, ‘age’: 14, ‘other’: ”, ‘name’: ‘Xiao Ming’}
4

[‘high’, ‘age’, ‘other’, ‘name’]
<type ‘list’>
[167, 14, ”, ‘Xiao Ming’]
<type ‘list’>
[(‘high’, 167), (‘age’, 14), (‘other’, ”), (‘name’, ‘Xiao Ming’)]
<type ‘list’>
———-0
(‘high’, 167)
(‘age’, 14)
(‘other’, ”)
(‘name’, ‘Xiao Ming’)
———-1
Xiao Ming
{‘high’: 167, ‘age’: 14, ‘other’: ”, ‘name’: ‘Xiao Ming’}
Xiao Ming
{‘high’: 167, ‘age’: 14, ‘other’: ”, ‘grades’: None, ‘name’: ‘Xiao Ming’}

2.字典的操作:增、删、改、查、复制和创建
dicA={‘high’: 167, ‘age’: 14, ‘other’: ”, ‘grades’: None, ‘name’: ‘Xiao Ming’}
#字典的操作
#增
dicA[“sex”]=”boy”#用[]直接添加字典关键字和值,且增加在字典尾部
print(dicA)#打印增加后字典

dicB={“class”:1,34:”ge”}
dicA.update(dicB)#将dicB更新到dicA中
print(dicA)#打印更新后结果
print(“———-2”)

#删
del dicA[“other”]#通过del删除关键词和值
print(dicA)#打印删除后字典

dicA.pop(“age”)#索引并删除关键字为“age”的键值对
print(dicA)#打印pop后字典

dicA.popitem()#索引并删除字典*新加入的键值对
print(dicA)#打印popitem后结果
print(“———-3”)

#改
dicA[“sex”]=”girl”#通过关键字将性别改为girl,它会覆盖原来值
print(dicA)#打印改后字典
print(“———-4”)

#查
print dicA.has_key(“sex”)#查询字典中是否存在关键字sex
print(“———-5”)

#字典的复制
print(dicA.copy())#打印复制后字典
print(“———-6”)

#创建新字典
dic1={}#新建一个空的字典
dic2=dic1.fromkeys((“hair”,”leg”),”long”)#对空的字典添加”hair”和“leg”两个关键值,并将值都赋为long
print(dic2)#打印新建字典

#清除
dic2.clear()#清除dic2
print(dic2)

打印结果:

{‘name’: ‘Xiao Ming’, ‘age’: 14, ‘sex’: ‘boy’, ‘high’: 167, ‘grades’: None, ‘other’: ”}
{34: ‘ge’, ‘name’: ‘Xiao Ming’, ‘age’: 14, ‘sex’: ‘boy’, ‘high’: 167, ‘grades’: None, ‘other’: ”, ‘class’: 1}
———-2
{34: ‘ge’, ‘name’: ‘Xiao Ming’, ‘age’: 14, ‘sex’: ‘boy’, ‘high’: 167, ‘grades’: None, ‘class’: 1}
{34: ‘ge’, ‘name’: ‘Xiao Ming’, ‘sex’: ‘boy’, ‘high’: 167, ‘grades’: None, ‘class’: 1}
{‘name’: ‘Xiao Ming’, ‘sex’: ‘boy’, ‘high’: 167, ‘grades’: None, ‘class’: 1}
———-3
{‘name’: ‘Xiao Ming’, ‘sex’: ‘girl’, ‘high’: 167, ‘grades’: None, ‘class’: 1}
———-4
True
———-5
{‘high’: 167, ‘class’: 1, ‘grades’: None, ‘name’: ‘Xiao Ming’, ‘sex’: ‘girl’}
———-6
{‘hair’: ‘long’, ‘leg’: ‘long’}
{}

ArcGIS开发arcpy教程
07-25
<p> arcgis数据处理过程中使用到的arcpy脚本,基础教程。有助于gis从业人员开发arcpy脚本便捷处理数据。提供基础与案例应用结合方式,讲解arcpy知识。 </p> <p> 1.python基础 </p> <p> 2.基本图形创建 </p> <p> 3.shapefile数据操作 </p> <p> 4.常见数据txt.csv,json,excel数据操作 </p> <p> 5.工具打包 </p> <p> (1)添加脚本方式 </p> <p> (2)ArcCatalog添加代码方式 </p> <p> 6.arcpy案例应用 </p> <p> (1)几何图形面polygon,点point之间关系应用 </p> <p> (2)面四至坐标提取应用 </p> <p> (3)面求交,找出*大相交面应用 </p> <p> (4)逐条记录导出应用 </p> <p> (5)批量裁剪影像应用 </p> <p> (6)栅格数据批量定义投影以及工具打包应用 </p> <p> <br /></p>
arcgis二次开发arcpy视频教程(持续更新中……)
yGIS
3253
https://edu.csdn.net/course/detail/25535 01. 课程概况 02. ArcPy开发-1-python基础–环境搭建、*个程序、练习 03. ArcPy开发-2-python基础–表达式,数据类型、语句、变量 04. ArcPy开发-3-python基础–字符串 05. ArcPy开发-4-python基础–列表 06. ArcPy…

爱码士看,月亮在跳舞:好文章2 小时前回复

爱码士LaoYuanPython:欢迎博主加入CSDN!欢迎博主到本人的Python专栏来交流!2 小时前回复

相关推荐
python笔记之ArcPy简介
baoqian1993的博客
2万+
第1章 ArcPy简介 1.1什么是 ArcPy? ArcPy 是一个以成功的 arcgisscripting 模块为基础并继承了 arcgisscripting 功能进而构建而成的站点包。目的是为以实用高效的方式通过 Python 执行地理数据分析、数据转换、数据管理和地图自动化创建基础。 该包提供了丰富纯正的 Python 体验,具有代码自动完成功能(输入关键字和点即可获得
ArcPy*章-Python基础
28
学习Arcpy,从零开始积累。1.代码注释: python中,说明部分通常使用注释来实现: 方式: # 或者 ## + 注释部分内容2. 模块导入: 方式: import Eg: import arcpy import os3.变量:(python中定义变量,不需要先声明变量类型,只需要直接命名和赋值即可) Eg: mapsize = “22 x 34″4.内置数据类型:…
python3使用arcpy_Python & ArcPy – 随笔分类 – McDelfino – 博客园
weixin_39622138的博客
109
随笔分类 – Python & ArcPyPython 学习,主要是 ArcGIS 里面有用到!摘要:利用 sklearn.feature_extraction.text 中的 CountVectorizer 来实现 首先获取所有的文本信息 然后将文本信息转化为从 0 开始的数字 获取转换后的字符向量 参见如下代码: >>> text_01 = “My name is A…
视频教程-ArcGIS开发arcpy教程-其他
weixin_33329105的博客
229
ArcGIS开发arcpy教程 从事数字城市、智慧城市、地质灾害、警务等国土…
从Python到空间分析Arcpy || 1.2.1 数据类型是什么,有哪些
jieyoudata的博客
66
今天开始介绍python中的数据类型,这是python中*基础也是*核心的内容,计划分为4-5个小节来介绍。 总的来说python中的数据类型可以分为 9大类(很容易记的数字)。 数值类型(Number) 数值类型又包括:int 整型 float 浮点型 complex复数类型,常用前2个 布尔型(Bool ) 布尔类型在python中是用关键字来定义的,False 和True ,请注意两个单词的*个字母都要大写,python 语言本身是对大小写敏感的语言,A和a在python中是两个不同的对象,bool
access数据放到list中_从Python到空间分析Arcpy || 1.2.1 数据类型

【总结】2020年*新算法工程师技术路线图

这是一份写给公司算法组同事们的技术路线图,其目的主要是为大家在技术路线的成长方面提供一些方向指引,配套一些自我考核项,可以带着实践进行学习,加深理解和掌握。

%title插图%num

工程师能力层级概览

对于不同级别的算法工程师技能要求,我们大致可以分成以下几个层级:

  • 初级:可以在一些指导和协助下独立完成开发任务。具体到算法方面,需要你对于工具框架,建模技术,业务特性等方面有一定的了解,可以独立实现一些算法项目上的需求。
  • 中级:可以基本独立完成一个项目的开发与交付。在初级工程师的基础上,对于深入了解技术原理的要求会更高,并且能够应对项目中各种复杂多变的挑战,对于已有技术和工具进行改造适配。在整体工程化交付方面,对于代码质量,架构设计,甚至项目管理方面的要求会开始显现。另外从业务出发来评估技术选型和方案也变得尤为重要。
  • 高级:可以独立负责一条产品线的运作。在中级工程师的基础上,需要更广阔的技术视野与开拓创新能力,定义整个产品线的前进方向。解决问题已经不是关键,更重要的是提出和定义问题,能够打造出在业界具有*性和差异性的产品,为公司创造更大的价值。

事实上对于不同层级的工程师,非技术部分的要求都有一定占比。本文主要聚焦在技术路线图上,对于其他方面的学习进阶路线不会做覆盖。

阅读建议

以下内容分工程基础,算法基础,算法工程交叉,工程深入方向,算法深入方向几个部分,在各个部分内部会进一步区分一些主题。在各个主题内部,也是有深入程度的区别的,不过限于篇幅没有进行详细的说明。建议学习路线可以先把两个基础部分与工作中较为相关的内容做一个整体基础的夯实,然后可以在后续交叉和深入方向的主题中选择感兴趣的进行深入了解和学习,过程中发现基础部分欠缺的,可以再回到基础部分查漏补缺,迭代前行。

工程基础

编程语言

Python

Python 是算法工程师日常工作中*常用的语言,应该作为必须掌握的一门技术。大致的学习路线如下:

  • 学习掌握 Python 的基本语法,可以通过各类入门教程来看,个人推荐《Learn Python the Hard Way》。
  • 自我考核:能够读懂大多数的内部项目及一些开源项目代码的基本模块,例如 pandas, sklearn 等。
  • 学习 Python 的编程风格,建议学习观远内部的 Python 代码规范。
  • 自我考核:编写的代码符合编码规范,能够通过各类 lint 检查。
  • Python 进阶,这方面有一本非常著名的书《Fluent Python》,深入介绍了 Python 内部的很多工作原理,读完之后对于各类疑难问题的理解排查,以及语言高级特性的应用方面会很有帮助。另外动态语言元编程这块,《Ruby 元编程》也是一本非常值得推荐的书。
  • 自我考核:能够读懂一些复杂的 Python 项目,例如 sqlalchemy 中就大量使用了元编程技巧。在实际工程项目中,能够找到一些应用高级技巧的点进行实践,例如基于 Cython 的性能优化等。
  • 领域应用,Python 的应用相当广泛,在各个领域深入下去都有很多可以学习的内容,比如 Web 开发,爬虫,运维工具,数据处理,机器学习等。这块主要就看大家各自的兴趣来做自由选择了,个人推荐熟悉了解一下 Python web 开发,测试开发相关的内容,开拓视野。
  • 自我考核:以 Web 开发和测试开发为例,尝试写一个简单的 model serving http 服务,并编写相应的自动化测试。

Scala/Java

Java 目前是企业级开发中*常用的软件,包括在大数据领域,也是应用*广泛的语言,例如当年的 Hadoop 生态基本都是基于 Java 开发的。Scala 由于其函数式编程的特性,在做数据处理方面提供了非常方便的 API,也因为 Spark 等项目的火热,形成了一定的流行度。在进行企业级的软件开发,高性能,大规模数据处理等方面,JVM 上的这两门语言有很大的实用价值,值得学习。

顺带一提,Scala 本身是一门非常有意思的语言,其中函数式编程的思想与设计模式又是非常大的一块内容,对于拓宽视野,陶冶情操都是挺不错的选择。

考虑到算法工程师的工作内容属性,这边给出一个 Scala 的学习路线:

  • 学习掌握 Scala 的基本语法,开发环境配置,项目编译运行等基础知识。这里推荐 Coursera 上 Martin Odersky 的课程,《快学 Scala》或《Programming in Scala》两本书也可以搭配着浏览参考。
  • 自我考核:能使用 Scala 来实现一些简单算法问题,例如 DFS/BFS。或者使用 Scala 来处理一些日常数据工作,例如读取日志文件,提取一些关键信息等。
  • 学习使用 Scala 来开发 Spark 应用,推荐 edX 上的《Big Data Analytics Using Spark》或者 Coursera 上的《Big Data Analytics with Scala and Spark》,另外有些相关书籍也可以参考,比如《Spark 快速大数据分析》等。
  • 自我考核:能够使用 Spark 的 Scala API 来进行大规模的数据分析及处理,完成 lag feature 之类的特征工程处理。
  • JVM 的原理学习,Scala/Java 都是 JVM 上运行的优秀语言,其背后是一个非常大的生态,包括在 Web,Android,数据基础架构等方面有广泛的应用。JVM 相比 Python 虚拟机,发展更加成熟,有一套非常完善的 JDK 工具链及衍生的各类项目,便于开发者 debug,调优应用。这方面推荐学习周志明的《深入理解 Java 虚拟机》。
  • 自我考核:理解 JVM GC 原理,能通过 JDK 中相关工具或者优秀的第三方工具如 arthas 等,排查分析 Spark 数据应用的资源使用情况,GC profiling,hot method profiling 等,进而进行参数优化。
  • 计算机语言理论。Programming Language 作为计算机科学的一个重要分支,包含了很多值得深入研究的主题,例如类型论,程序分析,泛型,元编程,DSL,编译原理等。这方面的很多话题,在机器学习方面也有很多实际应用,比如 TVM 这类工作,涉及到大量编译原理的应用,知乎大佬 “蓝色” 也作为这个领域的专家在从事深度学习框架相关的工作。llvm, clang 作者 Chris Lattner 也加入 Google 主导了 Swift for Tensorflow 等工作。Scala 作为一门学术范非常强的语言,拥有*佳的 FP,元编程等能力支持,强大的类型系统包括自动推理,泛型等等高级语言特性,相对来说是一门非常 “值得” 学习的新语言,也是一个进入 PL 领域深入学习的 “gateway drug” 🙂 对这个方面有兴趣的同学,可以考虑阅读《Scala 函数式编程》,《冒号课堂》,以及 Coursera 上《Programming Languages》也是一门非常好的课程。另外只想做科普级了解的同学,也可以读一读著名的《黑客与画家》感受一下。

C/C++/Rust

当前流行的算法框架,例如 TensorFlow, PyTorch, LightGBM 等,底层都是基于 C++ 为主要语言进行实现的。但是 C++ 本身过于复杂,使用场景也比较有限制,建议只需要达到能够读懂一些基础的 C++ 代码逻辑即可。在系统级开发领域,目前有一门新语言逐渐崛起,连续几年被 StackOverflow 投票评选为程序员*喜爱的语言:Rust。从设计理念和一些业界应用(例如 TiKV)来看还是非常不错的,但是我也没有深入学习了解过,就不做具体推荐了。这方面建议的学习内容包括经典的《The C Programming Language》以及 Rust 官方的:https://github.com/rust-lang/rustlings

  • 自我考核:能够读懂 LightGBM 里对于 tweedie loss 的相关定义代码。

操作系统

基本概念

我们所编写的算法应用,都是通过操作系统的环境运行在物理硬件之上的。在实际运作过程中,会碰到不少相关的问题,例如为什么程序报了资源不足的错误,为什么 notebook 在浏览器里打不开,为什么进程 hang 住了没有响应等等,都需要一些操作系统的知识来帮助理解和分析问题,*终排查解决。操作系统涵盖的内容比较多,建议一开始只需要了解一些主要概念(例如硬件结构,CPU 调度,进程,线程,内存管理,文件系统,IO,网络等),对于整体图景有一些感觉即可。后续碰到了实际问题,可以再在各个部分深入学习展开。优秀的学习资料也有很多,基本都是大部头,重点推荐《深入理解计算机系统》,《Operating Systems: Three Easy Pieces》,以及《现代操作系统》。

  • 自我考核:能够基本明确运行一个模型训练任务过程中,底层使用到的硬件,操作系统组件,及其交互运作的方式是如何的。

Linux 基础

平时工作中*常用的两个操作系统 CentOS 和 macOS,都是 Unix/Linux 系的,因此学习掌握相关的基础知识非常重要。一些必须掌握的知识点包括:Shell 与命令行工具,软件包管理,用户及权限,系统进程管理,文件系统基础等。这方面的入门学习资料推荐《鸟哥的 Linux 私房菜》,基本涵盖了 Linux 系统管理员需要掌握知识的方方面面。进阶可以阅读《Unix 环境高级编程》,对于各种系统调用的讲解非常深入,可以为后续性能调优等高级应用打下基础。

  • 自我考核:开发一个 shell 小工具,实现一些日常工作需求,例如定时自动清理数据文件夹中超过一定年龄的数据文件,自动清理内存占用较大且运行时间较久的 jupyter notebook 进程等。

深入应用

工作中碰到的疑难问题排查,性能分析与优化,系统运维及稳定性工程等方面,都需要较为深入的计算机体系和操作系统知识,感兴趣的同学可以针对性的进行深入学习。以性能优化为例,可以学习经典的《性能之巅》,了解其中的原理及高级工具链。像其中的系统调用追踪 (strace),动态追踪(systemtap, DTrace, perf, eBPF) 等技术,对于操作系统相关的问题排查都会很有帮助。

  • 自我考核:能够分析定位出 LightGBM 训练过程中的性能瓶颈,精确到函数调用甚至代码行号的级别。

软件工程

算法与数据结构

暂时先把这块放到软件工程模块下。这里指的算法是计算机科学中的经典算法,例如递归,排序,搜索,动态规划等,有别于我们常说的机器学习算法。这块的学习资料网上有非常多,个人当年是通过普林斯顿的算法课 (需要有 Java 基础) 入门,后来又上了斯坦福的算法分析与设计,开拓了一些视野。书籍方面推荐新手从《算法图解》入门,然后可以考虑阅读 Jeff Erickson 的《Algorithms》,或者选择上面提到的网课。另外像《编程珠玑》,《编程之美》等也可以参阅,里面有不少问题的巧妙解法。除了从书本中学习,还可以直接去 LeetCode 等网站进行实战操作进行练习提高。

  • 自我考核:能够设计相关的数据结构,实现一个类似 airflow 中点击任意节点向后运行的功能。

代码规范

从初级程序员到中高级程序员,其中比较大的一个差异就是代码编写习惯上,从一开始写计算机能理解,能够运行成功的代码,逐渐演化到写人能够理解,易于修改与维护的代码。在这条学习路径上,首先需要建立起这方面的意识,然后需要在实战中反复思考和打磨自己的代码,评判和学习其它优秀的项目代码,才能逐渐精进。推荐的学习书籍有《编写可读代码的艺术》,一本非常短小精悍的入门书籍,后续可以再慢慢阅读那些经典大部头,例如《Clean Code》,《Code Complete》,《The Pragmatic Programmer》等。这方面 Python 也有一本比较针对性的书籍《Effective Python》,值得一读。

  • 自我考核:审视自己写的项目代码,能发现并修正至少三处不符合*佳编码实践的问题。

设计模式

在代码架构方面,设计模式是一个重要的话题,对于日常工作中出现的许多典型场景,给出了一些解决方案的“套路”。这方面*著名的书当属 GoF 的《设计模式》,不过个人并不十分推荐,尤其是以 Python 作为主要工作语言的话,其中很大部分的设计模式可能并不需要。入门可以浏览一下这个网站掌握一些基本概念:https://refactoringguru.cn/design-patterns/python ,后续可以考虑阅读《Clean Architecture》,《重构》等相关数据,理解掌握在优化代码架构过程中思考的核心点,并加以运用。Python 相关的设计模式应用,还可以参考《Python in Practice》。

  • 自我考核:在项目中,找到一处可以应用设计模式的地方,进行重构改进。

质量保障

对于需要实际上线运行的软件工程,质量保障是非常重要的一个环节,能够确保整个产品按照期望的方式进行运作。在机器学习项目中,由于引入了数据这个因素,相比传统的软件测试会有更高的难度,也是业界还在摸索前进的方向。建议可以先阅读《单元测试的艺术》或《Google 软件测试之道》,大致理解软件测试的一些基本概念和运作方式,在此基础上可以进一步阅读 Martin Fowler 对于机器学习领域提出的 CD4ML 中相关的测试环节,学习 sklearn,LightGBM 等开源库的测试开发方式,掌握机器学习相关的质量保障技术能力。

  • 自我考核:在项目中,实现基础的数据输入测试,预测输出测试。

项目管理

软件工程推进过程中,项目管理相关的技能方法与工具运用也非常的关键。其中各种研发流程与规范,例如敏捷开发,设计评审,代码评审,版本管控,任务看板管理等,都是实际项目推进中非常重要的知识技能点。这方面推荐学习一本经典的软件工程教材《构建之法》,了解软件项目管理的方方面面。进一步来说广义的项目管理上的很多知识点也是后续深入学习的方向,可以参考*客时间上的课程《项目管理实战 20 讲》。

  • 自我考核:在某个负责项目中运用项目管理方法,完成一个实际的需求评估,项目规划,设计与评审,开发执行,项目上线,监控维护流程,并对整个过程做复盘总结。

高级话题

软件工程师在技能方向成长的一条路线就是成为软件架构师,在这个方向上对于技能点会有非常高的综合性要求,其中也有不少高级话题需要深入学习和了解,例如技术选型与系统架构设计,架构设计原则与模式,宽广的研发知识视野,高性能,高可用,可扩展性,安全性等等。有兴趣的同学可以了解一下*客时间的《从 0 开始学架构》这门课,逐渐培养这方面的视野与能力。另外如《微服务架构设计模式》还有领域驱动设计方面的一系列书籍也值得参考学习。

  • 自我考核:设计一个算法项目 Docker 镜像自动打包系统。

算法基础

数据分析

数学基础

在进行算法建模时,深入了解数据情况,做各类探索性分析,统计建模等工作非常重要,这方面对一些数学基础知识有一定的要求,例如概率论,统计学等。这方面除了经典的数学教材,也可以参考更程序员向的《统计思维》,《贝叶斯方法》,《程序员的数学 2》等书籍。

  • 自我考核:理解实际项目中的数据分布情况,并使用统计建模手段,推断预测值的置信区间。

可视化

在进行数据分析时,可视化是一个非常重要的手段,有助于我们快速理解数据情况,发掘数据规律,并排查异常点。对于各种不同类型的数据,会对应不同的可视化*佳实践,如选择不同的图表类型,板式设计,分析思路编排,人机交互方式等等。另一方面,可视化与数据报告也是我们与不同角色人群沟通数据 insights 的一个重要途径,需要从业务角度出发去思考可视化与沟通方式。这方面可以参考《Storytelling with Data》,《The Visual Display of Quantitative Information》等经典数据,同时也需要培养自己的商业背景 sense,提升沟通能力。

  • 自我考核:对内沟通方面,能使用可视化技术,分析模型的 bad case 情况,并确定优化改进方向。对外沟通方面,能独立完成项目的数据分析沟通报告。

误差分析与调优

在做算法模型调优改进中,需要从数据分析的基础上出发来决定实验方向,这么做有几个好处:

  • 从分析出发指导调优更有方向性,而不是凭经验加个特征,改个参数碰运气。哪怕是业务方提供的信息,也*好是有数据分析为前提再做尝试,而不是当成一个既定事实。
  • 由分析发现的根源问题,对于结果验证也更有帮助。尤其在预测的数据量*大情况下,加一个单一特征很可能总体只有千分位准确率的提升,无法确定是天然波动还是真实的提升。但如果有分析的前提,那么我们可以有针对性的看对于这个已知问题,我们的调优策略是否生效,而不是只看一个总体准确率。
  • 对于问题的彻底排查解决也更有帮助,有时候结果没有提升,不一定是特征没用,也可能是特征代码有 bug 之类的问题。带着数据分析的目标去看为什么这个特征没有效果,是模型没学到还是特征没有区分度等,有没有改进方案,对于我们评判调优尝试是否成功的原因也更能彻查到底。
  • 数据分析会帮助我们发现一些额外的问题点,比如销量数据清洗处理是不是有问题,是不是业务本身有异常,需要剔除数据等。

这方面在业界有一些关于误差分析的探索研究,不过大多数都是基于分类问题的,例如《Identifying Unknown Unknowns in the Open World》,《A Characterization of Prediction Errors》等。可以在了解这些研究的基础上,结合具体的业务情况,深入思考总结误差分析的思路与方法论。

  • 自我考核:在项目中形成一套可以重复使用的误差分析方案,能够快速从预测输出中定位到目前模型*重要的误差类别,并一定程度上寻找到根本原因。

机器学习基础

传统机器学习

这块大家应该都非常熟悉了,初阶的学习路线可以参考周志华老师的《机器学习》,涵盖了机器学习基础,常用机器学习方法,和一些进阶话题如学习理论,强化学习等。如果希望深化理论基础,可以参考经典的《PRML》,《ESL》和《统计学习方法》。在实战中,需要综合业务知识,算法原理,及数据分析等手段,逐渐积累形成建模调优的方法论,提高整体实验迭代的效率和成功率。

  • 自我考核:结合实际业务和机器学习理论知识,挖掘项目中算法表现不够好的问题,并通过算法改造进行提升或解决。

深度学习

近些年兴起的深度学习,已经成为机器学习领域一个非常重要的分支,在各个应用方向发挥了很大的作用。相对于传统机器学习,对于特征工程要求的降低成了其核心优势。另一方面,深度学习对于大数据量,大规模算力的应用能力很强,也一定程度上提升了整体的产出效果。由于理论方面的研究稍显落后,深度学习在实际应用中对于使用者的经验技能要求相对比较高,需要有大量的实战经验才能达到比较理想的效果。这方面的学习资料推荐 Keras 作者的《Deep Learning with Python》,以及《Hands-on Machine Learning with Scikit-Learn and TensorFlow》,而在理论方面推荐著名的“花书”《Deep Learning》。在学习理论原理的基础上,尤其要注意在实际算法应用中,能够通过观察各种指标与数据分析,找到提升模型的操作改进方向。

  • 自我考核:能够在实际项目中,使用深度学习模型,达到接近甚至超过传统 GBDT 模型的精确度效果,或者通过 ensemble,embedding 特征方式,提升已有模型的精度。

领域建模

目前我们的业务领域在时间序列预测,自然语言处理,推荐等方面,其它类似图像,搜索,广告等领域也都有各自的一些领域建模方法。在时间序列领域,包括了传统时序模型,如 ARIMA, Prophet,机器学习模型,如划动窗口特征构建方法结合 LightGBM,及深度学习模型,例如 LSTM,seq2seq,transformer 等。这方面可以参考 Kaggle 上相关比赛的方案分享,以及 Amazon,Uber,天猫等有类似业务场景公司的分享资料。其它领域也是类似,通过了解历史技术演进,相关比赛,业界的方案分享与开源项目,会议论文来逐渐掌握学习建模方法,结合实际业务进行实践尝试,积累起更加体系性的个人知识技能。

  • 自我考核:在项目中复现一个 Kaggle 获胜方案,检验其效果,分析模型表现背后的原因,并尝试进行改进。

算法框架

数据处理框架

在项目实施过程中,会需要各类复杂的数据处理操作,因此熟练掌握此类框架就显得尤为重要。目前行业的标准基本上会参照 Pandas DataFrame 的定义,在数据量较大的情况下,也有许多类似的框架,如 Spark,Dask,Modin,Mars 等支持分布式运行的 DataFrame,以及 cuDF,Vaex 等提升单机性能的改进实现。这方面经典的书籍可以参考 Wes McKinney 的《Python for Data Analysis》,在掌握基础数据操作的基础上,可以进而了解窗口函数,向量化性能优化等高级话题。另外 SQL 也可以做非常复杂的数据处理工作,有不少公司例如阿里会以 SQL 为主来构建数据处理流程,感兴趣的同学也可以学习一下 SQL 中各种高级计算的使用及优化方法。

  • 自我考核:在已有项目中,能把至少三个使用 apply 方法的 pandas 处理修改成向量化运行,并测试性能提升。使用 window function 或其它方案来实现 lag 特征,减少 join 次数。

机器学习框架

机器学习方面的新框架层出不穷,一方面我们需要掌握经典框架的使用方式,理解其模块构成,接口规范的设计,一定程度上来说其它新框架也都需要遵循这些业界标准框架的模块与接口定义。另一方面对于新框架或特定领域框架,我们需要掌握快速评估,上手使用,并且做一定改造适配的能力。一些比较经典的框架有:

  • 通用机器学习:scikit-learn,Spark ML,LightGBM
  • 通用深度学习:Keras/TensorFlow,PyTorch
  • 特征工程:tsfresh, Featuretools,Feast
  • AutoML:hyperopt,SMAC3,nni,autogluon
  • 可解释机器学习:shap,aix360,eli5,interpret
  • 异常检测:pyod,egads
  • 可视化:pyecharts,seaborn
  • 数据质量:cerberus,pandas_profiling,Deequ
  • 时间序列:fbprophet,sktime,pyts
  • 大规模机器学习:Horovod,BigDL,mmlspark
  • Pipeline:MLflow, metaflow,KubeFlow,Hopsworks

一般的学习路径主要是阅读这些框架的官方文档和 tutorial,在自己的项目中进行尝试使用。对于一些核心接口,也可以阅读一下相关的源代码,深入理解其背后的原理。

  • 自我考核:在 LightGBM 框架下,实现一个自定义的损失函数,并跑通训练与预测流程。

其它框架

其它比较常见且与算法工程师日常工作会有一些联系的有 Web 框架,爬虫框架等,*具有代表性的当属 Flask 和 scrapy。这两者背后各自又是很大一块领域,尤其 web 开发更是保罗万象。感兴趣的同学还可以了解一下一些新兴的基于 Python3 的框架,例如 FastAPI,其背后借鉴的许多现代框架的思想设计,包括数据验证,序列化,自动文档,异步高性能等,开拓一下知识面。

  • 自我考核:实现一个简单的 model serving http 服务。

算法工程交叉

大规模算法运行

分布式训练

在很多项目中,数据量达到十亿级以上的情况下,单机训练会难以支撑。因此分布式训练也是实际工程落地中非常重要的一个主题。分布式训练涉及到多机的通讯协同方式,优化算法的改造,数据及模型的并行与聚合,以及框架的选择和运维等话题,具体可以参考《分布式机器学习》。另外对于分布式系统,也可以参阅《数据密集型应用系统设计》这本神作,了解其背后原理。

  • 自我考核:能够在多机上进行亿级数据的 GBDT 模型训练与预测。

高性能计算

在做大规模的数据训练与推理时,近些年涌现出许多高性能计算优化的方法,例如从硬件方面,有各种超线程技术,向量化指令集,GPGPU,TPU 的应用等,从软件方面,有针对数值计算场景的 OpenBLAS,有自动并行化的 OpenMP,有各种 codegen,JIT 技术下的运行时优化等。这方面可以学习的方向也很多,从基础的并行编程,编译原理及优化的知识开始,到 CUDA,OpenMP 的应用(例如 Nvidia 的 cuDNN,还有 LightGBM 中也用到了 OpenMP),Codegen,JIT 等技术在 Spark,TVM 等项目中的使用等,建议有深度性能优化需求时可以往这些方向做调研和学习。

  • 自我考核:能够通过 LLVM JIT 来优化实现 Spark window function 的执行性能。

模型加速领域

这个方向分两个部分,一块是模型训练方面,能够做到加速,例如使用大 batch size,迁移学习,持续的在线 / 增量学习等手段,另一块在模型预测方面,也有很多加速需求,比如模型参数量优化,模型压缩,混合精度,知识蒸馏等技术手段,都是为了做到更高性能,更低资源消耗的模型预测推理。这方面业界有各个方向的文章和技术实现可以参考,比如经典的《Training ImageNet in 1 Hour》,MobileNet,TensorRT,二值网络等。

  • 自我考核:在典型的销量预测场景中实现增量训练与预测。

MLOps

编排调度

包含各类 pipeline 的编排与调度能力的支持,包括数据 pipeline,训练 pipeline 和 serving pipeline 等。这方面比较常用的框架工具有 Airflow,DolphinScheduler,Cadence 等,需要掌握其基本的工作原理和使用方式,并能够应用于离线实验与线上运行。

  • 自我考核:使用 Airflow 完成一个标准的项目 pipeline 搭建与运行。

数据集成

相对于传统的 DevOps,机器学习项目*大的区别在于数据方面的依赖会更加显著与重要。这方面的话题包括数据血缘,数据质量保障,数据版本控制等,有各类工具可以借鉴使用,例如数据版本管理方面的 DVC,数据质量方面的 TFX Data Validation,Cerberus,Deequ 等。在方法论层面,《The ML Test Score》中给出了不少数据相关的具体测试方法,值得参考学习。

  • 自我考核:在项目中实现输入数据的分布测试,特征工程测试及特征重要性准入测试。

实验管理

这部分也是 ML 项目的独特之处,在开发过程中有大量的实验及相应的结果输出需要记录,以指导后续调整优化的方向,并选择*优结果来进行上线部署。这方面可以参考的项目有 MLflow,fitlog,wandb 等。当然对于单独的项目来说,可能 online Excel 就能满足需求了 🙂

  • 自我考核:在实际项目中实行一套标准的实验记录手段,并能从中找出各类实验尝试带来的精度提升的 top 5 分别是哪些操作。

Serving

目前我们的 serving 大多数是离线 batch 预计算的形式,所以主要依赖的技术手段是各类离线 inference 的方法,例如直接使用 model predict 接口,使用 mmlspark 等做大规模并行 inference 等。如果涉及到在线 serving,情况会更加复杂,例如在线 pipeline 的运行,实时特征获取,low latency/high throughput 的 serving 服务等,可以参考 TF Serving,MLeap,H2O,PredictionIO,PMML/PFA/ONNX 等开发标准模型格式等。

  • 自我考核:部署一个实时预测服务,能够根据用户输入产生相应的预测结果。

CI/CD

软件工程中的持续集成,持续部署已经成为一种标准实践,在算法项目中,额外引入了数据这个维度的复杂性,带来了一些新的挑战。在这个方向上,几个主要话题包括自动化测试,pipeline 打包部署,持续监控运维等,可以参考 Martin Fowler 关于 CD4ML 的文章。工具系统层面,可以学习传统的 Jenkins,也有一些新选择例如 CircleCI,GoCD,VerCD(Uber)等。

  • 自我考核:通过 Jenkins 实现 pipeline 自动测试,打包,上线流程。

系统监控

在整个项目上线后,需要对系统的各个环节进行监控,并对各种异常情况作出响应。例如输入数据的监控,判别测试数据与训练数据的分布是否有偏移,整个运行 pipeline 的监控,判别是否有运行失败抛出异常的情况,对于预测输出的监控,确保没有异常的预测输出值,也包括对于系统计算资源等方面的监控,确保不会因为资源不足导致业务受到影响等。在监控信息收集,基础上,还需要配套一系列的自动告警通知,日志追踪排查等。这方面的工具框架包括 TF data validation 这类专门针对算法项目的新产品,也有 elasicsearch + kibana 这类传统产品。

  • 自我考核:将三个项目中做过的问题排查改造成常规监控手段,支持自动的问题发现,告警通知,如有可能,提供自动化或半自动化的问题排查解决方案。

MLOps 系统

MLOps 整体是一个比较大的话题,在这方面有很多产品和系统设计方面的实践可以参考学习。例如 Uber 的 Michelangelo 系列文章,Facebook 的 FBLearner,neptune.ai,dataiku,domino 等,虽然没有开源,但是其背后的很多设计理念,演进思考,白皮书等都非常值得我们学习。在开源界也有很多可以参考的项目,例如 MLflow,Kubeflow,Metaflow,TFX 等,可以学习他们的设计理念,Roadmap,以及实现细节等。

  • 自我考核:总结各个 MLOps 产品的功能模块矩阵对比,能够根据项目需求来进行产品选型与使用。

工程深入方向

数据库

数据库原理

在平时工作中,我们有大量的场景需要用到数据库。从客户数据的对接,数据集的管理和使用,到各种业务系统的数据表设计及优化等,都需要对数据库的运作原理,适用场景,运维使用,性能优化等方面有一定的了解。常见的需要掌握的概念有 OLTP vs OLAP,事务,索引,隔离级别,ACID 与 CAP 理论,数据同步,数据分片,SQL 语法,ORM 等。从底层原理看,会涉及到数据,索引,及日志等存储引擎方面,以及各种计算查询引擎,包括分布式系统的设计与实现。这方面推荐的学习资料有《数据库系统内幕》及《数据密集型应用系统设计》。

  • 自我考核:能够理解 SQL 执行计划,并能够根据执行计划来做索引或查询调优。

关系型数据库

目前常用的关系型数据库主要是 MySQL 和 PostgreSQL,主要需要掌握的是日常的一些 SQL 操作,例如 DML(增删改查),DDL(创建表,修改索引等),DCL(权限相关)。在此基础上还可以进一步了解一些如数据类型,高级计算,存储引擎,部署运维,范式概念与表结构设计等方面的话题。对于高级话题这块,推荐《高性能 MySQL》与《高可用 MySQL》。

  • 自我考核:在 MySQL 中设计相关表结构,存储实际项目中的一系列中间数据集。

NoSQL 数据库

常用的 NoSQL 数据库有几类,KV 存储(Redis),文档数据库(MongoDB),Wide-column 存储(Cassandra,HBase)以及图数据库(Neo4j)。在目前我们的算法项目中,比较有可能会用到的主要是 Redis 这类 KV 存储(也可能把 Cassandra 之类当泛 KV 来用),或者更新一点的类似 Delta Lake 的存储系统。建议学习了解一下这类 KV 存储,以及分布式数据库的常见操作方式,以及基础的运维排查,性能优化方法。

  • 自我考核:考虑一个线上模型服务的场景,用户输入作为基础特征,使用类似 Redis 的 KV 系统,实现实时获取其它特征,并进行模型预测。

云计算

基础架构

IT 系统总体的发展趋势在往云计算方向演进,即使是自建的基础设施,也会采用云计算的一套构建方式,让开发者不用过多的关注底层计算存储资源的部署运维。对于应用开发者来说,需要了解一些基础架构方面的知识,例如各类虚拟化及容器技术,配置管理,容器编排等,便于在日常工作中使用相关技术来管理和发布应用。从工具层面看,Docker 与 k8s 等技术发展速度较快,主要还是根据官方文档来学习为主。浙大之前出版的《Docker – 容器与容器云》一书中有一些更深入的话题的探讨,另外《Kubernetes in Action》中也值得一读。从方法论层面看,《Infrastructure as Code》和《Site Reiliability Engineering》是两本非常不错的学习资料。与算法应用结合的虚拟化,运维,持续集成等都是比较新的领域,需要我们探索出一条可行路线。

  • 自我考核:对于已有的算法项目,总结制定一套开发,测试,发布,运维的标准流程,且尽可能自动化执行。

分布式存储

前些年*流行的分布式存储是脱胎于 Google 经典的 GFS 论文实现的 HDFS,不过随着硬件技术的发展,计算存储分离思想的逐渐兴起,不但灵活性更高,成本更低,且各自架构的复杂度也大大降低了。因此目前更建议学习简单的 object store 形式的分布式存储,例如 s3,minio 等。在此基础上的一些存储系统,例如 Delta Lake,提供了事务,高效的 upsert,time travel 等功能,也值得关注与学习。原理方面,还是推荐《数据密集型应用设计》这本。

  • 自我考核:在项目中实现不同机器能够访问同一个 s3 路径的文件,并进行正常的数据读写,模型文件读写等功能。

分布式计算

大数据时代的分布式计算的鼻祖来自于 Google 经典的 MapReduce 论文,后续在 Hadoop 系统中做了开源实现,在前几年是非常火热的一项技术。目前业界的主流是 Spark 和 Flink,前者在批处理计算中处于霸者地位,后者是流处理领域的*者。目前我们的业务应用中,Spark 是比较常用的分布式计算引擎,其基本操作相关内容比较简单,参考官方文档或者《Spark 快速大数据分析》即可。后续的主要难点会有大数据量下的问题排查与性能调优,执行复杂计算或与 Python 相关 UDF 的交互配合方式等。这方面需要对 Spark 的系统架构,内部原理有一定了解,例如 master,worker,driver,executor 等之间的关系,lazy evaluation,DAG 的 lineage 与 stage 概念,shuffle 优化,wholestage codegen 等技术细节。这方面暂时没有找到比较好的资料,主要还是依赖实际问题解决的经验积累。

  • 自我考核:用 Spark 来实现项目中的特征工程,并在一定数据量情况下取得比单机 Pandas 更好的性能效果。

其它话题

其它云服务基础设施还包括分布式数据库,消息队列,zk/raft 分布式协作系统,虚拟网络,负载均衡等。这些话题离算法应用方面会比较远一些,基本上达到遇到需求时会使用的能力即可,在这里不做展开。

算法深入方向

AutoML

超参优化

自动化机器学习中比较传统的一块是超参数优化,进而可以推广到整个 pipeline 的超参优化,包括数据预处理,特征工程,特征选择,模型选择,模型调优,后处理等部分。目前业界应用比较广泛的技术手段主要是随机搜索,贝叶斯优化,进化算法,Hyperband/BOHB 等,在特征工程方面有 Featuretools,tsfresh,AutoCrossing 等自动化特征工程工具。学术界有一些进一步的探索研究,包括 multi-fidelity 优化,多任务优化,HPO 结合 ensemble learning,pipeline planning,data diff 自动数据分布探测等方面。可以参考 http://automl.org 上的各类参考资料与书籍进行学习了解。主要难点包括 automl 算法的泛化能力,scalability,整体 pipeline 组合的搜索与生成,针对不同学习算法的自动优化手段等。

  • 自我考核:了解超参优化的基础概念,能够在项目中应用框架工具来实现模型超参的贝叶斯优化流程。

元学习

Meta learning 是近年来非常活跃的一个新兴领域,其主要思路是希望能通过元学习模型方法,去积累建模调优的先验知识,跨任务推断模型效果并 warm start 新的训练任务,或者指导学习算法来进行更高效的具体任务的训练过程。这方面在工业界的主要应用基本上集中在建模调优先验知识的积累方面,比如通过一系列公开数据集搜索寻找出表现较好的起始参数,用于指导在新任务上做超参优化的起始搜索点。学术研究中除了 configuration space 的研究,还包括从 learning curve 中进行学习推断,元特征提取与建模,HTN planning 在 pipeline 构建中的应用,以及 MAML 等 few-shot learning 方向的探索。这方面推荐 Lilian Weng 的一系列文章(https://lilianweng.github.io/lil-log/2018/11/30/meta-learning.html),以及 http://automl.org 网站上的资料。

  • 自我考核:设计一系列 meta feature 与 meta learning 手段,实现对新任务的参数选择的初始化。

NAS

AutoML 领域比较火,但也是比较特别的一个方向,目前需要大量的计算资源投入才能做这方面的研究与尝试,因此主要建议了解一下这个方向的一些工作即可,不做深入探索学习。

AutoML 系统

自动化机器学习相关的框架工具也非常多,比较有代表性的框架有 auto-sklearn(来自 http://automl.org 团队),nni(microsoft),auto-gluon(amazon),H2O,ray tune 等,在工具级别也有如 hyperopt,SMAC3,featuretools 等。可以通过学习这些工具框架,了解 AutoML 系统的架构与实现方式,并应用到实际项目中。

  • 自我考核:使用一种 AutoML 系统来进行项目的模型自动优化,并与手工优化的结果进行比较,看是否有所提升,及寻找背后的原因。

模型解释

模型解释技术

主要有三个方面,一是模型本身的解释性,例如线性回归,决策树等,模型结构简单,根据其原理,可以直接对预测结果,特征使用等方面给出解释。另外一些复杂模型,例如 EBM,神经网络,Bayesian rule lists,SLIMs 等,也可以利用一些本身的特性给出一些解释,例如 GradCAM 方法等。二是模型无关的解释方法,包括经典的 PDP,ICE 等特征图,LIME 等 surrogate model 方法,以及基于博弈论的 Shapley 方法。三是基于 sample 的解释方法,例如 conterfactual explanations,adversarial examples,prototypes,influential instances,kNN 等,不过看起来这类方法对于计算的开销一般都会比较大,不太容易在工程中实现落地。这方面的资料可以学习《Interpretable Machine Learning》和《Explainable AI》(关于深度学习的内容会更多)。另外学术界也有很多前沿探索,比如针对模型解释的降维工作,自动的时间序列分析及报告生成,因果模型,模型公平性及社会影响等方面,可以保持关注。

  • 自我考核:理解 LIME,Shapley 的运作原理,并分析其局限性,尝试提出改进方案。

模型解释应用

从工具框架方面,有许多可以使用的开源项目,例如微软的 interpret,eli5,shap,AIX360 等。另外也有一些非传统意义上的模型解释,例如 manifold,tensorboard 这类模型 debugging 工具,自动化的误差分析与模型改进方案,因果模型框架,模型公平性评估与纠正工具等,都可以涵盖在广义的模型解释领域中。在工具基础上,如何结合业务领域知识,给出更有针对性的解释方案,也是值得思考深挖的方向。

  • 自我考核:使用 shap,eli5 等工具来进行模型解释,并在此基础上形成面向开发者的模型 debug,误差分析及改进方案,或形成面向业务的 what-if 分析看板。

总结

目前机器学习应用领域还在高速发展与演进过程中,除了上述提到的技能方向,后续很可能会不断有新的主题引入进来,需要练就快速学习并应用落地的能力。在掌握前面编程,软件工程,机器学习的基础上,后半部分的研究方向,大家可以根据个人兴趣,选择几个进行深入探索与实践。仅阅读相关书籍和文章,只能对知识内容有一个初步的认识,必须要通过深入的动手实践,反复试错思考和修正,才能逐渐内化为自己的技能,并构建起较为坚实的知识体系。

原文链接:https://zhuanlan.zhihu.com/p/192633890

IOS学习之UITableView表视图控件初步

表视图这个控件学习的时候,发现是目前我接触到*复杂的组件。

在Android中也提供了类似表视图的控件叫ListView。

原生的ListView,支持的操作其实很有限,数据的条目展示,点击或是长按的操作。

后来慢慢的衍生出来的索引,分区,动态改变指定条目位置等。

到了IOS发现,原来都是这些设计概念全是从IOS的表视图移植过去的吧。

因此,IOS的表视图是个挺丰富的控件

以下文章内容我基本是这么个流程划分

*简单的表视图——》自定义Cell表——》可编辑表——》可动态移动表

以下是配合Navigation导航条控件演示的tableView各种实现。

一:基础表视图

我们看下表视图一个大致的界面模型

首先是navc的顶级视图

%title插图%num

这个视图控制器的代码基本很前面提到的导航那章一样,只是多了一个数组容器来保存要显示的三个二级视图控制器

看下m文件

[cpp] view plain copy

  1. // 
  2. //  NonoFirstLevelViewController.m 
  3. //  NavTest 
  4. // 
  5. //  Created by Nono on 12-4-26. 
  6. //  Copyright (c) 2012年 NonoWithLilith. All rights reserved. 
  7. // 
  8. #import “NonoFirstLevelViewController.h” 
  9. #import “NonoSecondLevelViewController.h” 
  10. #import “SimpleTableViewController.h” 
  11. #import “CustomCellViewController.h” 
  12. #import “EditViewController.h” 
  13. @interface NonoFirstLevelViewController ()
  14. @end
  15. @implementation NonoFirstLevelViewController
  16. @synthesize controllers = _controllers;
  17. #pragma 实现头文件中自定义方法; 
  18. – (void)initAllSecondControllers:(NSMutableArray *)array
  19. {
  20.     SimpleTableViewController *controller1 = [[SimpleTableViewController alloc] init];
  21.     [controller1 setTitle:@“简单表视图”];
  22.     [array addObject:controller1];
  23.     [controller1 release];
  24.     CustomCellViewController *controller2 = [[CustomCellViewController alloc] init];
  25.      [controller2 setTitle:@“自定义cell视图”];
  26.     [array addObject:controller2];
  27.     [controller2 release];
  28.     EditViewController *controller3 = [[EditViewController alloc] init];
  29.     [controller3 setTitle:@“可编辑视图”];
  30.     [array addObject:controller3];
  31.     [controller3 release];
  32. }
  33. – (id)initWithStyle:(UITableViewStyle)style
  34. {
  35.     self = [super initWithStyle:style];
  36.     if (self) {
  37.     }
  38.     return self;
  39. }
  40. – (void)viewDidLoad
  41. {
  42.     [super viewDidLoad];
  43.     self.title = @“表视图Demo”;
  44.     //实例化一个可变数组 
  45.     NSMutableArray *array = [[NSMutableArray alloc] init ];// 
  46.     self.controllers = array;
  47.     [array release];
  48.     [self initAllSecondControllers:self.controllers];
  49. }
  50. – (void)viewDidUnload
  51. {
  52.     [super viewDidUnload];
  53. }
  54. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  55. {
  56.     return (interfaceOrientation == UIInterfaceOrientationPortrait);
  57. }
  58. #pragma mark – Table view data source 
  59. – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  60. {
  61.     return [self.controllers count];
  62. }
  63. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  64. {
  65.     static NSString *CellIdentifier = @“FirstLevelCell”;
  66.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  67.     if (cell == nil) {
  68.         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  69.     }
  70.     NSUInteger row = [indexPath row];
  71.     NonoSecondLevelViewController *controller = [self.controllers objectAtIndex:row];
  72.     cell.textLabel.text = [controller title];
  73.     return cell;
  74. }
  75. #pragma mark – Table view delegate 
  76. – (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  77. {
  78.     NSUInteger row = [indexPath row];
  79.     NonoSecondLevelViewController  *secondVC = [self.controllers objectAtIndex:row];
  80.      [self.navigationController pushViewController:secondVC animated:YES];
  81. }
  82. @end
  1. //
  2. // NonoFirstLevelViewController.m
  3. // NavTest
  4. //
  5. // Created by Nono on 12-4-26.
  6. // Copyright (c) 2012年 NonoWithLilith. All rights reserved.
  7. //
  8. #import “NonoFirstLevelViewController.h”
  9. #import “NonoSecondLevelViewController.h”
  10. #import “SimpleTableViewController.h”
  11. #import “CustomCellViewController.h”
  12. #import “EditViewController.h”
  13. @interface NonoFirstLevelViewController ()
  14. @end
  15. @implementation NonoFirstLevelViewController
  16. @synthesize controllers = _controllers;
  17. #pragma 实现头文件中自定义方法;
  18. – (void)initAllSecondControllers:(NSMutableArray *)array
  19. {
  20. SimpleTableViewController *controller1 = [[SimpleTableViewController alloc] init];
  21. [controller1 setTitle:@“简单表视图”];
  22. [array addObject:controller1];
  23. [controller1 release];
  24. CustomCellViewController *controller2 = [[CustomCellViewController alloc] init];
  25. [controller2 setTitle:@“自定义cell视图”];
  26. [array addObject:controller2];
  27. [controller2 release];
  28. EditViewController *controller3 = [[EditViewController alloc] init];
  29. [controller3 setTitle:@“可编辑视图”];
  30. [array addObject:controller3];
  31. [controller3 release];
  32. }
  33. – (id)initWithStyle:(UITableViewStyle)style
  34. {
  35. self = [super initWithStyle:style];
  36. if (self) {
  37. }
  38. return self;
  39. }
  40. – (void)viewDidLoad
  41. {
  42. [super viewDidLoad];
  43. self.title = @“表视图Demo”;
  44. //实例化一个可变数组
  45. NSMutableArray *array = [[NSMutableArray alloc] init ];//
  46. self.controllers = array;
  47. [array release];
  48. [self initAllSecondControllers:self.controllers];
  49. }
  50. – (void)viewDidUnload
  51. {
  52. [super viewDidUnload];
  53. }
  54. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  55. {
  56. return (interfaceOrientation == UIInterfaceOrientationPortrait);
  57. }
  58. #pragma mark – Table view data source
  59. – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  60. {
  61. return [self.controllers count];
  62. }
  63. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  64. {
  65. static NSString *CellIdentifier = @“FirstLevelCell”;
  66. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  67. if (cell == nil) {
  68. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  69. }
  70. NSUInteger row = [indexPath row];
  71. NonoSecondLevelViewController *controller = [self.controllers objectAtIndex:row];
  72. cell.textLabel.text = [controller title];
  73. return cell;
  74. }
  75. #pragma mark – Table view delegate
  76. – (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  77. {
  78. NSUInteger row = [indexPath row];
  79. NonoSecondLevelViewController *secondVC = [self.controllers objectAtIndex:row];
  80. [self.navigationController pushViewController:secondVC animated:YES];
  81. }
  82. @end

顶视图类基本就是一个导航作用。

线面我么先看*简单的这条目

简单表视图:%title插图%num

[cpp] view plain copy

  1. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3.     //控件复用 
  4.     static NSString *CellIdentifier = @“simpleCell”;
  5.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  6.     if (cell == nil) {
  7.         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  8.     }
  9.     NSUInteger row = [indexPath row];
  10.     NSString *string = [self.data objectAtIndex:row];
  11.     cell.textLabel.text = string;
  12.     //这个可以定义item右端小图标显示风格,默认是none; 
  13.     //cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; 
  14.     [string release];
  15.     return cell;
  16. }
  1. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3. //控件复用
  4. static NSString *CellIdentifier = @“simpleCell”;
  5. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  6. if (cell == nil) {
  7. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  8. }
  9. NSUInteger row = [indexPath row];
  10. NSString *string = [self.data objectAtIndex:row];
  11. cell.textLabel.text = string;
  12. //这个可以定义item右端小图标显示风格,默认是none;
  13. //cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
  14. [string release];
  15. return cell;
  16. }

这边主要说如下几点:

1》。控件得复用,这个和Android很像,因此我们在获取cell对象时,先从原来得复用队列里查找(更具指定的标记,这点也告诉我们,我们可以设置多个标记),

若没有,那就新建一个

2》。整个tableview的style分两种,一种就是顶级视图界面的那种: self.tableView.style = UITableViewStylePlain,另一种就是这个视图的风格:

self.tableView.style = UITableViewStyleGrouped

3》.对于每个item,单元格样式使用了3个不同的单元格元素。依次左边开始有个图标,中间就是一个label,右侧会有一个详情栏。

4》。同样的对于每个cell也是有样式风格的 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]

针对3,4设置后得某种效果如下:

%title插图%num%title插图%num

左端可以自己敬爱个图标进去,黑体字就是文本label,灰色的是详细文本标签,小箭头图标是accessoryType

以下就是代码

[cpp] view plain copy

  1. static NSString *CellIdentifier = @“FirstLevelCell”;
  2. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  3. if (cell == nil) {
  4.     cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
  5. }
  6. NSUInteger row = [indexPath row];
  7. NonoSecondLevelViewController *controller = [self.controllers objectAtIndex:row];
  8. cell.textLabel.text = [controller title];
  9. cell.detailTextLabel.text = @“什么情况”;
  10. //这个可以定义item右端小图标显示风格,默认是none; 
  11. cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
  12. return cell;
  1. static NSString *CellIdentifier = @“FirstLevelCell”;
  2. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  3. if (cell == nil) {
  4. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
  5. }
  6. NSUInteger row = [indexPath row];
  7. NonoSecondLevelViewController *controller = [self.controllers objectAtIndex:row];
  8. cell.textLabel.text = [controller title];
  9. cell.detailTextLabel.text = @“什么情况”;
  10. //这个可以定义item右端小图标显示风格,默认是none;
  11. cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
  12. return cell;

默认风格的cell是不能显示详情标签内容的。

其实很多效果,代码都走一边就看出来了,具体就自己改动下代码就ok了

二:自定义的Cell

%title插图%num%title插图%num

自定义的cell,xib实现

%title插图%num%title插图%num

基本没什么好说的,看下该类额控制器文件

[cpp] view plain copy

  1. // 
  2. //  CustomCellViewController.m 
  3. //  NavTest 
  4. // 
  5. //  Created by Nono on 12-5-4. 
  6. //  Copyright (c) 2012年 NonoWithLilith. All rights reserved. 
  7. // 
  8. #import “CustomCellViewController.h” 
  9. @interface CustomCellViewController ()
  10. @end
  11. @implementation CustomCellViewController
  12. @synthesize customCell = _customCell;
  13. – (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  14. {
  15.     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  16.     if (self) {
  17.         // Custom initialization 
  18.     }
  19.     return self;
  20. }
  21. – (void)viewDidLoad
  22. {
  23.     [super viewDidLoad];
  24.     NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@“陈凯”,@“Nono”,@“Lilith”,@“窗前明月光”,@“疑是地上霜”,@“举头望明月”,@“低头思故乡”,@“锄禾日当午”,@“汗滴禾下土”,@“谁知盘中餐”,@“粒粒皆幸苦”,nil];
  25.     self.data = array;
  26.     [array release];
  27.     // Do any additional setup after loading the view from its nib. 
  28. }
  29. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  30. {
  31.     return (interfaceOrientation == UIInterfaceOrientationPortrait);
  32. }
  33. #pragma mark_ 
  34. #pragma 数据源方法 
  35. – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  36. {
  37.     return [self.data count];
  38. }
  39. // Row display. Implementers should *always* try to reuse cells by setting each cell’s reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier: 
  40. // Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls) 
  41. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  42. {
  43.     static NSString *CellIdentifier = @“CustomCell”;
  44.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  45.     if (cell == nil) {
  46.         NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@“CustomCell” owner:self options:nil];
  47.         if([nib count] > 0){
  48.             cell = self.customCell;
  49.             cell.backgroundColor = [UIColor redColor];
  50.         }else{
  51.             NSLog(@“加载 nib文件失败”);
  52.         }
  53.     }
  54.     NSUInteger row = [indexPath row];
  55.     NSString *string = [self.data objectAtIndex:row];
  56.     UILabel *customlabel =(UILabel*) [cell viewWithTag:11];
  57.     customlabel.text = string;
  58.     [string release];
  59.     return cell;
  60. }
  61. @end
  1. //
  2. // CustomCellViewController.m
  3. // NavTest
  4. //
  5. // Created by Nono on 12-5-4.
  6. // Copyright (c) 2012年 NonoWithLilith. All rights reserved.
  7. //
  8. #import “CustomCellViewController.h”
  9. @interface CustomCellViewController ()
  10. @end
  11. @implementation CustomCellViewController
  12. @synthesize customCell = _customCell;
  13. – (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  14. {
  15. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  16. if (self) {
  17. // Custom initialization
  18. }
  19. return self;
  20. }
  21. – (void)viewDidLoad
  22. {
  23. [super viewDidLoad];
  24. NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@“陈凯”,@“Nono”,@“Lilith”,@“窗前明月光”,@“疑是地上霜”,@“举头望明月”,@“低头思故乡”,@“锄禾日当午”,@“汗滴禾下土”,@“谁知盘中餐”,@“粒粒皆幸苦”,nil];
  25. self.data = array;
  26. [array release];
  27. // Do any additional setup after loading the view from its nib.
  28. }
  29. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  30. {
  31. return (interfaceOrientation == UIInterfaceOrientationPortrait);
  32. }
  33. #pragma mark_
  34. #pragma 数据源方法
  35. – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  36. {
  37. return [self.data count];
  38. }
  39. // Row display. Implementers should *always* try to reuse cells by setting each cell’s reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
  40. // Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
  41. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  42. {
  43. static NSString *CellIdentifier = @“CustomCell”;
  44. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  45. if (cell == nil) {
  46. NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@“CustomCell” owner:self options:nil];
  47. if([nib count] > 0){
  48. cell = self.customCell;
  49. cell.backgroundColor = [UIColor redColor];
  50. }else{
  51. NSLog(@“加载 nib文件失败”);
  52. }
  53. }
  54. NSUInteger row = [indexPath row];
  55. NSString *string = [self.data objectAtIndex:row];
  56. UILabel *customlabel =(UILabel*) [cell viewWithTag:11];
  57. customlabel.text = string;
  58. [string release];
  59. return cell;
  60. }
  61. @end

提几个注意点:

1》。cell的xib文件得拥有者设置成该类,在该类得头文件中定义一个输出口。

2》 我们看到cell的xib文件有3个label视图我们能看到,其实还有一个没有title的label视图,也就我们要动态添加数据的那个视图,

在xib文件中需要给他设置一个tag,这样我们在代码里才能根据tag找出该对象(和Android中得id很像)。这边我定义了11,所以

 UILabel *customlabel =(UILabel*) [cellviewWithTag:11];

    customlabel.text = string;

3》。xib文件加载,我是根据书上得列子方法。根据应用的束来获取。

4》。哦,还有点就是 static NSString *CellIdentifier = @”CustomCell”;。这个在xib文件得指定器中定义,因为原本我们新建一个cell是有个传入指定标签,
而现在这个新建一个cell说白了就是直接从xib中加载一个实例化了,那么指定器怎需要在xib中定义下。
对于cell简单的自定义就是这样。

三:可编辑的tableView(删除,添加,移动)

%title插图%num%title插图%num

[cpp] view plain copy

  1. // 
  2. //  EditViewController.m 
  3. //  NavTest 
  4. // 
  5. //  Created by Nono on 12-5-4. 
  6. //  Copyright (c) 2012年 NonoWithLilith. All rights reserved. 
  7. // 
  8. #import “EditViewController.h” 
  9. @interface EditViewController ()
  10. @end
  11. @implementation EditViewController
  12. @synthesize edittableView;
  13. – (void)editButtonPressed:(id)sender
  14. {
  15.     [self.edittableView setEditing:!self.edittableView.editing animated:(YES)];
  16.     if (edittableView.editing) {
  17.         [self.navigationItem.rightBarButtonItem setTitle:@“完成”];
  18.     }else {
  19.         [self.navigationItem.rightBarButtonItem setTitle:@“编辑”];
  20.     };
  21.     NSLog(@“点击了按钮”);
  22. }
  23. – (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  24. {
  25.     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  26.     if (self) {
  27.         // Custom initialization 
  28.     }
  29.     return self;
  30. }
  31. – (void)viewDidLoad
  32. {
  33.     [super viewDidLoad];
  34.     NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@“陈凯”,@“Nono”,@“Lilith”,@“窗前明月光”,@“疑是地上霜”,@“举头望明月”,@“低头思故乡”,@“锄禾日当午”,@“汗滴禾下土”,@“谁知盘中餐”,@“粒粒皆幸苦”,nil];
  35.     self.data = array;
  36.     [array release];
  37.     UIBarButtonItem *rigthButton = [[UIBarButtonItem alloc]  initWithTitle:@“编辑”  style:UIBarButtonItemStyleBordered  target:self  action:@selector(editButtonPressed:)];
  38.     self.navigationItem.rightBarButtonItem = rigthButton;
  39.     //self.navigationItem.prompt = @”加载”; 
  40.     [rigthButton release];
  41.     // Do any additional setup after loading the view from its nib. 
  42. }
  43. – (void)viewDidUnload
  44. {
  45.     [super viewDidUnload];
  46.     // Release any retained subviews of the main view. 
  47.     // e.g. self.myOutlet = nil; 
  48. }
  49. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  50. {
  51.     return (interfaceOrientation == UIInterfaceOrientationPortrait);
  52. }
  53. #pragma mark – Table view data source 
  54. – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  55. {
  56.     // Return the number of rows in the section. 
  57.     return [self.data count];
  58. }
  59. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  60. {
  61.     static NSString *CellIdentifier = @“editLevelCell”;
  62.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  63.     if (cell == nil) {
  64.         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  65.     }
  66.     NSUInteger row = [indexPath row];
  67.     NSString *string = [self.data objectAtIndex:row];
  68.     cell.textLabel.text = string;
  69.     [string release];
  70.     return cell;
  71. }
  72. #pragma 实现数据源协议中一些关于编辑操作方法 
  73. – (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  74. {
  75.     //是否可以编辑,即是tableView setEditing的前提;默认是yes,实现这个方法估计主要是选择性的编辑条目。 
  76.     return YES;
  77. }
  78. – (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
  79. {
  80.     //同理默认其实就是yes,移动模式(会显示可以触摸得移动button)必须是在实现了下面这个方法才有效,否则及时yes了,移动模式条也是不显示的,简单的说,你不能执行移动操作 
  81.     return YES;
  82. }
  83. //移动操作 
  84. – (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
  85. {
  86.     //拖动得思路就是先备份选中行,删除原来那份,将备份的一份插入到目标行 
  87.     NSUInteger fromRow = [sourceIndexPath row];
  88.     NSUInteger toRow = [destinationIndexPath row];
  89.     id ob = [[self.data objectAtIndex:fromRow] retain];
  90.     [self.data removeObjectAtIndex:fromRow];
  91.     [self.data insertObject:ob atIndex:toRow];
  92.     [ob release];
  93. }
  94. – (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  95. {
  96.       NSUInteger row = [indexPath row];
  97.     //提交操作完的编辑 
  98.     if (editingStyle == UITableViewCellEditingStyleDelete) {
  99.         [self.data removeObjectAtIndex:row]; //删除操作 
  100.         [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
  101.     }
  102.     if (editingStyle == UITableViewCellEditingStyleInsert) {
  103.         [self.data insertObject:@“插入数据” atIndex:row];//插入操作 
  104.         [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
  105.     }
  106. }
  107. #pragma 实现tableView委托中一些方法 
  108. – (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
  109. {
  110.     //设置可编辑得样式:系统提供了三种,一种是删除,一种是插入,一种时是none 
  111.     NSInteger row = [indexPath row];
  112.     if(row %2 == 0)//这边做了小处理,间隔显示删除和插入 
  113.     {
  114.         return UITableViewCellEditingStyleDelete;
  115.     }
  116.     return UITableViewCellEditingStyleInsert;
  117. }
  118. @end
  1. //
  2. // EditViewController.m
  3. // NavTest
  4. //
  5. // Created by Nono on 12-5-4.
  6. // Copyright (c) 2012年 NonoWithLilith. All rights reserved.
  7. //
  8. #import “EditViewController.h”
  9. @interface EditViewController ()
  10. @end
  11. @implementation EditViewController
  12. @synthesize edittableView;
  13. – (void)editButtonPressed:(id)sender
  14. {
  15. [self.edittableView setEditing:!self.edittableView.editing animated:(YES)];
  16. if (edittableView.editing) {
  17. [self.navigationItem.rightBarButtonItem setTitle:@“完成”];
  18. }else {
  19. [self.navigationItem.rightBarButtonItem setTitle:@“编辑”];
  20. };
  21. NSLog(@“点击了按钮”);
  22. }
  23. – (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  24. {
  25. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  26. if (self) {
  27. // Custom initialization
  28. }
  29. return self;
  30. }
  31. – (void)viewDidLoad
  32. {
  33. [super viewDidLoad];
  34. NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@“陈凯”,@“Nono”,@“Lilith”,@“窗前明月光”,@“疑是地上霜”,@“举头望明月”,@“低头思故乡”,@“锄禾日当午”,@“汗滴禾下土”,@“谁知盘中餐”,@“粒粒皆幸苦”,nil];
  35. self.data = array;
  36. [array release];
  37. UIBarButtonItem *rigthButton = [[UIBarButtonItem alloc] initWithTitle:@“编辑” style:UIBarButtonItemStyleBordered target:self action:@selector(editButtonPressed:)];
  38. self.navigationItem.rightBarButtonItem = rigthButton;
  39. //self.navigationItem.prompt = @”加载”;
  40. [rigthButton release];
  41. // Do any additional setup after loading the view from its nib.
  42. }
  43. – (void)viewDidUnload
  44. {
  45. [super viewDidUnload];
  46. // Release any retained subviews of the main view.
  47. // e.g. self.myOutlet = nil;
  48. }
  49. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  50. {
  51. return (interfaceOrientation == UIInterfaceOrientationPortrait);
  52. }
  53. #pragma mark – Table view data source
  54. – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  55. {
  56. // Return the number of rows in the section.
  57. return [self.data count];
  58. }
  59. – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  60. {
  61. static NSString *CellIdentifier = @“editLevelCell”;
  62. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  63. if (cell == nil) {
  64. cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  65. }
  66. NSUInteger row = [indexPath row];
  67. NSString *string = [self.data objectAtIndex:row];
  68. cell.textLabel.text = string;
  69. [string release];
  70. return cell;
  71. }
  72. #pragma 实现数据源协议中一些关于编辑操作方法
  73. – (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  74. {
  75. //是否可以编辑,即是tableView setEditing的前提;默认是yes,实现这个方法估计主要是选择性的编辑条目。
  76. return YES;
  77. }
  78. – (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
  79. {
  80. //同理默认其实就是yes,移动模式(会显示可以触摸得移动button)必须是在实现了下面这个方法才有效,否则及时yes了,移动模式条也是不显示的,简单的说,你不能执行移动操作
  81. return YES;
  82. }
  83. //移动操作
  84. – (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
  85. {
  86. //拖动得思路就是先备份选中行,删除原来那份,将备份的一份插入到目标行
  87. NSUInteger fromRow = [sourceIndexPath row];
  88. NSUInteger toRow = [destinationIndexPath row];
  89. id ob = [[self.data objectAtIndex:fromRow] retain];
  90. [self.data removeObjectAtIndex:fromRow];
  91. [self.data insertObject:ob atIndex:toRow];
  92. [ob release];
  93. }
  94. – (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  95. {
  96. NSUInteger row = [indexPath row];
  97. //提交操作完的编辑
  98. if (editingStyle == UITableViewCellEditingStyleDelete) {
  99. [self.data removeObjectAtIndex:row]; //删除操作
  100. [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
  101. }
  102. if (editingStyle == UITableViewCellEditingStyleInsert) {
  103. [self.data insertObject:@“插入数据” atIndex:row];//插入操作
  104. [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
  105. }
  106. }
  107. #pragma 实现tableView委托中一些方法
  108. – (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
  109. {
  110. //设置可编辑得样式:系统提供了三种,一种是删除,一种是插入,一种时是none
  111. NSInteger row = [indexPath row];
  112. if(row %2 == 0)//这边做了小处理,间隔显示删除和插入
  113. {
  114. return UITableViewCellEditingStyleDelete;
  115. }
  116. return UITableViewCellEditingStyleInsert;
  117. }
  118. @end

基本代码如上。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速