标签: iOS

《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的操作存储。

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

基本代码如上。

ios 调用webservice整理

学iphone开发有一段时间了,对于我这个新手来说,学习过程中,遇到不少问题,尤其是webservice的调用一直困扰了很久,赶在光棍前夕之迹,谈谈个人在webservice方面遇到的问题以及解决方案~~跟大家分享一下,对于那些高手来说可以略过~~

也不知道这是人生中的第几个光棍节了,没有妹子,很纠结,没有遇到对的人,在爱情的等待中渐渐的发霉~~~~~~~~

在这里也祝单身的男银、女银们,在光棍节脱”光”~~~~废话不多说~~切入正题

一、简述

要调用webservice你想到的是什么?

(1)写一个调用webservice的类(可以自已写,也可以下载第三方的类库)

(2)对webservice返回的xml解析(ios自带的xml解析真的很烦)

      我在网上度娘了很网,都是看到有些人,要么只提供webservice的类,简单的说明一下,对于返回的数据xml,一字不提~~~用起他们写的类来,真的很冒烟啊~~~~,网上找到的一些答案,我觉得很奇怪,大牛们经常会写一句不明不白的话,要写就写清楚,否则干脆不要写~~~让人希望又失望~~我不觉得这样有多牛B。。。反而我更喜欢把复杂的事件简单化~~~

我个人在调用webservice时遇到几个问题:

  a.webservice的命名空间问题?

     webservice默认的命名空间是:http://tempuri.org/

    我刚开始用得好好的,后来报soapAction不识别http://tempuri.org/~~~~那天花了一上午才找出这个错,原来有人改动了webservice的默认命名空间~~~我去你大爷的,改了命名空间,也不通知我,害我瞎折腾~~

  b.xml如何解析问题?

     本来是想用ios自带的类去解析xml的,网上查了一下,看别人写了一大片,脑门一片大汗,满脸黑线~~~,后来我用的是google写的GDataXML这个类库,还不错~~~,不过对于那些xml有命名空间的如何读取,各种坛子去提问,至今还没有人正确解答我~~~难道天朝就没有人知道吗?我失望至*,后来还是有如神助般醍醐灌顶~~~自已解决了,阿门~~~~

  c.异步调用时发生400错误?

    这个问题搞得我,头脑出现乱码,神经也被打茄了,你们猜这是啥原因~~~~

   soap原本要传递的信息是这样:

     <a>XXX</a><b>XXXX</b>

   我把它传递参数颠倒过来了,变成这样:

    <b>XXX</b><a>XXX</a>

    发现问题后,我傻楞般的笑了~~~~

  二、类的简要说明

(1)webservice(一个是使用NSURLConnection写的类,还有一个是ASIHttpRequest写的webservice调用类)

  a.同步调用

  b.异步调用

  c.返回数据的处理

     soap调用返回的数据经常放在:<webservice方法名Result>XXX</webservice方法名Result>中,我在webservice调用中已经直接提取出来了~~~提取出来的内容还是一个xml,这段内容的处理,我单独写在xml解析类里面

(2)xml解析类

1.读取<方法名Result>XXX</方法名Result>的内容

2.遍历xml的所有内容返回数组

三、使用配置

  (1)使用NSURLConnection写的webservice调用类,类名叫:WebServices.h

     包含的文件如下:

    a.AppConfigure.h文件的配置:

  b.google GDataXml配置如下:

     step1.先添加libxml2.dylib类库,不要告诉我这个都不会,拉去面壁~~~

    step2.操作如下图所示

  (2)借助ASIHttpRequest写的webservice调用类,类名叫:ServiceHelper.h

    跟(1)的配置一样,这里就不再重复说明,ASIHttpRequest要多添加几个类库,如下图所示

 四、文件下载

 (1)NSURLConnection类写的webservice调用类的所有文件下载

     http://dl.vmall.com/c0cld6ey99 [注:请下载demo里面的*新版本,有调整]

 (2)ASIHttpRequest类写的webservice调用类的所有文件下载

    http://dl.vmall.com/c0gx4evjva  [注:请下载demo里面的*新版本,有调整]

 五、demo下载      

demo下载地址: http://dl.vmall.com/c016bva6aw

开发者MAC电脑里的常见兵器

古人常以刀、枪、剑、戟、斧、钺、铲、叉、鞭、锏、锤、戈、镋、棍、槊、棒、矛、钯十八种兵器,样样精通,来形容一个人的武学技能get状态。在开发者的世界里,熟练掌握各种辅助工具,可以达到事半功倍,快速提高工作效率的效果。闲话不扯了,来看看究竟是什么。
道场

习武学道讲经论法总有个场所,这样容易把有意向来学习的人聚集起来,而且有助于信息的传播,精力的集中,技能的修炼,经验的交流和水平高下的切磋。工具的运用也是在一个特定的开发环境里才能发挥出比较好的效果。

我这里的开发环境(DOJO)是苹果公司Apple Inc.()2014年出的一款Mac Pro,具体配置如下:

MacBook Pro(Retina 显示屏,15 英寸,2014 年中)

处理器 2.2 GHz Intel Core i7

内存 16 GB 1600 MHz DDR3

图形卡 Intel Iris Pro 1536 MB

操作系统 OS X EI Capitan

这里描述的工具都是运行在这个硬件以及软件环境的,经过了2年多的开发经验/经历的靠谱验证,所以拿来分享给大家。

如果有朋友的研发环境和这里描述的不太一致,那么仅作参考吧,具体结合自己的情况。

为了方便检索,所以增加了工具分类。

开发*步是做什么?学习文档?画流程图?还是直接写代码?恩,先从学习文档开始吧。

文档查看

Dash

写代码的时候是不是有些API记不住,比如画椭圆该用哪个类?计算开平方用什么函数?怎么连接远程的mysql服务器检索数据?这个时候一般怎么办?问度娘?问谷歌?直接查看在线编程文档?

在国内问谷歌需要*,那么涉及到另外工具的使用。查看在线文档,如果记不住入口网址怎么办?放收藏里啊,如果入口改变了呢?还是需要问搜索引擎啊!那么问题来了,度娘乱贴小广告咋办?用Dash吧,一个APP搜罗了这个世界上几乎所有的编程语言文档,而且更新速度快。

软件主页以及下载地址:https://kapeli.com/dash

流程图设计
OmniGraffle有很多人推荐,不过笔者觉得这个软件太贵了,所以推荐了两款免费的软件流程设计工具。

XMind

主攻脑图(思维导图),流程图也支持,另外还有日程安排计划等额外的功能。

软件主页以及下载地址:http://www.xmind.net/

Gliffy Diagrams
并不是一个独立安装的APP,而是作为Chrome的插件,可以去Chrome的App Store下载安装,很轻量,运行速度快。

软件主页:https://www.gliffy.com/

文本编辑器
不仅限于代码编辑,一款好的编辑器会让你的编辑工作充满愉悦。

MacVim

为什么我一开始不推荐时下流行头牌Sublime呢?因为,我用vi/vim已经超过十年的时间了。当初在学校,vm/emacs二选一,我选择了更容易上手实践的vi,从此一直用它来查看/编译文本/代码。

软件主页以及下载地址:http://macvim-dev.github.io/macvim/

MacDown
一般代码查看和编辑用Vi就够了,剩下其他的文档,恩,现在大多数文章/文档采用的MarkDown语法编写,所以用一款MarkDown编辑器就够了。比如本文的编写,我用的MacDown编辑器,文章语法采用MarkDown语法。既然是MarkDown编辑器,那么有人会提到用Mou,笔者也试用过一段时间,遇到了一些问题,比如语法支持和界面显示,后来改用MacDown,觉得各方面都支持的不错,所以一直使用。

软件主页以及下载地址:http://macdown.uranusjr.com/

Sublime Text
如果你不是一路走着linux从事开发的话,估计很难对Vi/Emacs熟悉。那么,像note++或者ultraedit这类第三方编辑器会是你比较不错的选择。相比于集成开发环境IDE的笨重,运行慢和耗内存,选择一个轻量级的编辑器是在平时比较频繁的非常规查看/编辑代码/文档时一个不错的选择。那么,以前那些用第三方编辑器的用户都去哪儿了?应该就是这个Sublime Text了吧。

软件主页以及下载地址:http://www.sublimetext.com/

图片编辑器
写文章撸代码,除了文字的处理外,还需要有美图的点缀和衬托。更多时候,图是吸引流量和眼球的一种重要手段。

GIMP

为啥不用Adobe Photo Shop呢?太贵,太复杂。那么,好吧,这里笔者推荐用GIMP,PS该有的,它基本都有。

软件主页以及下载地址:http://www.gimp.org/

集成开发环境IDE
集成开发环境一般是集编辑,编译,链接,调试,版本管理和打包发布于一体的大型开发软件。它的特点是功能丰富,上手快,易操作。缺点也显而易见,笨重,运行速度慢,需要更多的CPU,内存资源。

Eclipse

老牌万金油型集成开发环境,上手快,支持几乎所有语言,但是近几年使用人数在下滑,逐渐转向Android Studio和IntelliJ IDEA了。

软件主页以及下载地址:http://www.eclipse.org

Android Studio
安卓程序猿专属开发环境。

软件主页以及下载地址:https://developer.android.com/studio/index.html

IntelliJ IDEA
Eclipse替代品,支持市面上大部分流行的开发语言和框架,上手快,界面更加人性化,现代集成开发环境的典范。

软件主页以及下载地址:https://www.jetbrains.com

Xcode
苹果公司官方唯一指定的Object-C与Swift集成开发环境。

软件主页以及下载地址:https://developer.apple.com/xcode/

分析调试类
APP写好了,安装到设备,但是从网络拉取图片显示失败了,怎么破?APP打安装包后想看下包里面到底有些啥?遇到这样的问题,这个的工具可以帮助你解决上面遇到的问题。

Wireshark

老牌网络抓包利器,各种平台都可以玩耍。

软件主页以及下载地址:https://www.wireshark.org/

tcpdump
这是一个命令行工具,可以看作是Wireshark的命令行版。

系统自带,无需额外安装。使用帮助

Charles

网络抓包利器加上代理功能,并支持自签名证书,所以可以用来在手机上抓取https的包。使用非常方便。付费软件,值得购买。

软件主页以及下载地址:https://www.charlesproxy.com/

JD-GUI
Java的class文件反编译神器,可以从二进制class文件查看它的Java源代码。

软件主页以及下载地址:http://jd.benow.ca/

JADX
JD-GUI的增强版,支持查看安卓apk/dex文件中反编译的Java源代码以及查看apk中其他文件的内容。

软件主页以及下载地址:https://github.com/skylot/jadx

版本管理
频繁的修改,反悔,记录需要管理,所以版本管理是必须的。

SourceTree

Atlassians出品的图形化版本管理工具,支持Git和Mercurial。

软件主页以及下载地:https://www.sourcetreeapp.com/

文件共享
从文件服务器(FTP, Samba etc.)下载资料或者上传文件到服务器上。

FileZilla

老牌Sourceforge开源文件传输软件。

软件主页以及下载地址:https://sourceforge.net/projects/filezilla/

证书管理
证书一般用于https加密,移动APP软件的安装文件签名。

Portecle

图像化管理证书的工具。

软件主页以及下载地址:https://sourceforge.net/projects/portecle/

截屏
截屏是强需求,没错。MAC下有截屏快捷键,只能截屏。一般用户截屏完毕后,不是马上发出去,而是做后期处理。

snip

截屏,编辑。

软件主页以及下载地址:http://snip.qq.com/

数据库
调试APP的时候,如果APP产生了数据,并且把数据保存在数据库(sqlite)中。如果想在开发主机上查看,可以用如下的工具。

Datum

查看sqlite数据库的内容。

软件主页以及下载地址:http://www.datumapps.com/

网络请求
有时候需要自己构造一个http网络请求(GET/POST),并查看输入输出的详细内容。简单的GET用浏览器可以代劳,复杂一点的需要浏览器安装插件支持。用如下的工具可以到达更好的效果。

wget

命令行工具。除了查看发送网络请求,查看结果外。另外一个用途是下载文件,特别是大文件,用浏览器下载经常会断线,而且断点续传做的不是太好。wget命令下载文件,支持断点续传,这个用起来不错。

curl

功能基本同wget,系统自带工具,无需安装。

rest-client

支持restful风格的网络请求构造,请求和结果相应。调试restful接口的好帮手。

软件主页以及下载地址:https://github.com/wiztools/rest-client

虚拟机&模拟器
我的电脑是MAC,可是招商银行的专业版没有MAC的客户端,肿么破?我想在MAC上看到安卓APP运行的情况,怎么办?安装一个虚拟机吧!

VirtualBox

老牌虚拟机软件,支持市面上几乎所有流行的操作系统。

软件主页以及下载地址:https://www.virtualbox.org/

genymotion
安卓模拟器,运行安卓APP如同在手机上一样的速度。

软件主页以及下载地址:https://www.genymotion.com/

MAC专用
有些工具是MAC系统专用的,比如用来管理苹果设备(iPad, iPhone, iMac, Mac etc.)配置文件的工具。

Apple Configurator

上App Store自行搜索下载安装。

使用帮助

服务端工具套件

有时候需要本地调试一些服务端提供的服务,或者是网站后台。这个时候一个开发/调试/模拟环境的选择变的重要了。还是那句不忘初衷的话,好的工具让你事半功倍!

Bitnami服务端套件

本地调试web服务器,nginx, mysql, php-fpm, etc.

软件主页以及下载地址:https://bitnami.com

Kitematic
Docker图形化管理工具。 软件主页以及下载地址:https://kitematic.com/

*利器
我要上谷歌搜索*新的Android开发文档和API,可是目前在国内用不了谷歌,怎么办?*吧!

ShadowsocksX

看标题,不多说,默默的下载,安装然后运行,配置,打开浏览器,访问谷歌,搜索Android就可以啦!

软件主页以及下载地址:https://sourceforge.net/projects/shadowsocksgui/

Lantern
如果上面那个不行,那么试试这个吧,不多说了。

软件主页以及下载地址:https://github.com/getlantern/lantern

iOS appstore审核被拒的各种原因以及相关条款

1. Terms and conditions(法律与条款)

1.1
As a developer of applications for the App Store you are bound by the terms of the Program License Agreement (PLA), Human Interface Guidelines (HIG), and any other licenses or contracts between you and Apple. The following rules and examples are intended to assist you in gaining acceptance for your App in the App Store, not to amend or remove provisions from any other agreement.(作为App Store的应用开发者,你必须接受如下条款:Program License Agreement (PLA),Human Interface Guidelines (HIG),以及任何你与apple签订的许可和合同。以下规则和示例是为了协助你的应用更快通过审核上架,而不是修正或取代之前的条款。)

 

2. Functionality(功能)

2.1
Apps that crash will be rejected(存在Crash(崩溃,死机)的应用会被拒。)

2.2
Apps that exhibit bugs will be rejected(存在明显bug的应用会被拒。)

2.3
Apps that do not perform as advertised by the developer will be rejected(不符合开发者描述的应用会被拒。)

2.4
Apps that include undocumented or hidden features inconsistent with the description of the App will be rejected(有未说明或隐藏特性或有悖描述的应用会被拒。)

2.5
Apps that use non-public APIs will be rejected(使用非公开API的应用会被拒。)

2.6
Apps that read or write data outside its designated container area will be rejected(试图读写非允许范围内的数据的应用会被拒。)

2.7
Apps that download code in any way or form will be rejected(试图以任何方式方法下载代码的应用会被拒。)

2.8
Apps that install or launch other executable code will be rejected(安装或运行其他可执行代码的应用会被拒。)

2.9
Apps that are “beta”, “demo”, “trial”, or “test” versions will be rejected(任何“beta”,“演示(demo)”,“试用(trial)”或“测试(test)”版本的应用会被拒。)

2.10
iPhone Apps must also run on iPad without modification, at iPhone resolution, and at 2X iPhone 3GS resolution(iPhone应用必须可以无条件运行在iPad上,支持普通iPhone分辨率和2倍iPhone 3GS分辨率。)

2.11
Apps that duplicate Apps already in the App Store may be rejected, particularly if there are many of them, such as fart, burp, flashlight, and Kama Sutra Apps.(任何与App Store中上架应用重复的应用会被拒,尤其是已经有了很多的:如放屁,打嗝,手电照明和爱经。)

2.12
Apps that are not very useful, unique, are simply web sites bundled as Apps, or do not provide any lasting entertainment value may be rejected(没有用处的应用,web页面简单组合的应用,或任何哗众取宠,不能提供娱乐价值的应用会被拒。)

2.13
Apps that are primarily marketing materials or advertisements will be rejected(纯粹用于市场推广或广告的应用会被拒。)

2.14
Apps that are intended to provide trick or fake functionality that are not clearly marked as such will be rejected(有意提供隐蔽或虚假功能,却又不能明显标示的应用会被拒。)

2.15
Apps larger than 50MB in size will not download over cellular networks (this is automatically prohibited by the App Store)(大于20MB的应用无法通过蜂窝网络下载安装(App Store自动处理)。)

2.16
Multitasking Apps may only use background services for their intended purposes: VoIP, audio playback, location, task completion, local notifications, etc.(多任务应用只允许在后台运行如下相应的服务:VoIP,音频播放,地理位置,任务记录,本地提醒等。)

2.17
Apps that browse the web must use the iOS WebKit framework and WebKit Javascript(应用只允许通过iOS WebKit框架和WebKit Javascript访问web页面。)

2.18
Apps that encourage excessive consumption of alcohol or illegal substances, or encourage minors to consume alcohol or smoke cigarettes, will be rejected(鼓励酗酒,使用违法药物,或诱导未成年人饮酒,吸烟的应用会被拒。)

2.19
Apps that provide incorrect diagnostic or other inaccurate device data will be rejected(提供错误的系统信息或设备数据的应用会被拒。)

2.20
Developers “spamming” the App Store with many versions of similar Apps will be removed from the iOS Developer Program(通过许多版本的类似应用对App Store造成干扰的开发者会被取消IDP身份。)

2.21
Apps that are simply a song or movie should be submitted to the iTunes store. Apps that are simply a book should be submitted to the iBookstore.(歌曲和电影应该提交到iTunes store。书籍应该提交到iBookstore。)

2.22
Apps that arbitrarily restrict which users may use the App, such as by location or carrier, may be rejected(随意通过位置或运营商来限制用户使用的应用会被拒。)

2.23
Apps must follow the iOS Data Storage Guidelines or they will be rejected(加入iCloud支持后,应用必须遵守iOS数据存储指南( iOS Data Storage Guidelines)否则将被拒。)

2.24
Apps that are offered in Newsstand must comply with schedules 1, 2 and 3 of the Developer Program License Agreement or they will be rejected(在Newsstand里提交的应用必须遵守Developer Program License Agreement的第1,2和3条,否则将被拒。)

 

2.25
Apps that display Apps other than your own for purchase or promotion in a manner similar to or confusing with the App Store will be rejected(与App Store类似的推荐或为其他应用做广告的应用将无法通过App Store审核。)

 

3. Metadata (name, descriptions, ratings, rankings, etc)(描述数据(名称,描述,评级,分类等))

3.1
Apps or metadata that mentions the name of any other mobile platform will be rejected(应用或者元数据中提到其他任意移动平台会被拒。)

3.2
Apps with placeholder text will be rejected(描述数据有未填写项,存留占位符文本会被拒。)

3.3
Apps with descriptions not relevant to the application content and functionality will be rejected(描述中提到与应用内容和功能无关信息会被拒。)

3.4
App names in iTunes Connect and as displayed on a device should be similar, so as not to cause confusion(应用在iTunes Connect与设备上显示的名称应该类似,否则会造成混淆。)

3.5
Small and large App icons should be similar, so as to not to cause confusion(不同尺寸的icon要一致,否则会造成混淆。)

3.6
Apps with App icons and screenshots that do not adhere to the 4+ age rating will be rejected(图标与截屏不符合4+年龄评级的应用会被拒。)

3.7
Apps with Category and Genre selections that are not appropriate for the App content will be rejected(应用的内容与所选分类和风格不符会被拒。)

3.8
Developers are responsible for assigning appropriate ratings to their Apps. Inappropriate ratings may be changed/deleted by Apple(开发者有责任把应用放到恰当的分级(Rating)。不恰当的评级可能会被Apple修改,甚至删除。)

3.9
Developers are responsible for assigning appropriate keywords for their Apps. Inappropriate keywords may be changed/deleted by Apple(开发者有责任给应用撰写恰当的关键词。不恰当的关键词可能会被Apple修改,甚至删除。)

3.10
Developers who attempt to manipulate or cheat the user reviews or chart ranking in the App Store with fake or paid reviews, or any other inappropriate methods will be removed from the iOS Developer Program(通过伪造,付费评价或其他非正规手段,获取App Store中较好的评价与星级的开发者会被取消IDP身份。)

3.11
Apps which recommend that users restart their iOS device prior to installation or launch may be rejected(任何提示需要用户重启iOS设备来安装或运行的应用会被拒。)

3.12
Apps should have all included URLs fully functional when you submit it for review, such as support and privacy policy URLs(应用在提交审核过程中,所有涉及到的URL都要处于正常运行状态,例如保密协议,相关支持页面等。)

4. Location(位置)

4.1
Apps that do not notify and obtain user consent before collecting, transmitting, or using location data will be rejected(未提示用户且获得用户允许之前收集,传输或使用位置数据的应用会被拒。)

4.2
Apps that use location-based APIs for automatic or autonomous control of vehicles, aircraft, or other devices will be rejected(使用location-based API来自动控制车辆,飞行器或其他设备的应用会被拒。)

4.3
Apps that use location-based APIs for dispatch, fleet management, or emergency services will be rejected(使用location-based API进行调度,队伍管理或应急服务的而应用会被拒。)

4.4
Location data can only be used when directly relevant to the features and services provided by the App to the user or to support approved advertising uses(位置数据只能用于应用提供的直接相关功能或服务,或者有授权的广告。)

5. Push notifications(提醒推送)

5.1
Apps that provide Push Notifications without using the Apple Push Notification (APN) API will be rejected(不使用Apple Push Notification(APN) API提供消息推送的应用会被拒。)

5.2
Apps that use the APN service without obtaining a Push Application ID from Apple will be rejected(使用APN服务却没从Apple获取一个Push Application ID的应用会被拒。)

5.3
Apps that send Push Notifications without first obtaining user consent will be rejected(在首次推送消息之前未取得的用户允许的应用会被拒。)

5.4
Apps that send sensitive personal or confidential information using Push Notifications will be rejected(使用提醒推送服务推送敏感的个人或机密信息的应用会被拒。)

5.5
Apps that use Push Notifications to send unsolicited messages, or for the purpose of phishing or spamming will be rejected(使用提醒推送发送主动消息,欺骗或干扰信息的应用会被拒。)

5.6
Apps cannot use Push Notifications to send advertising, promotions, or direct marketing of any kind(应用不可以使用提醒推送发送广告,活动或任何形式的直接推广信息。)

5.7
Apps cannot charge users for use of Push Notifications(应用不可以提供收费的提醒推送服务。)

5.8
Apps that excessively use the network capacity or bandwidth of the APN service or unduly burden a device with Push Notifications will be rejected(使用APN服务过度占用网络带宽或容量或通过提醒推送大量占用系统资源的应用会被拒。)

5.9
Apps that transmit viruses, files, computer code, or programs that may harm or disrupt the normal opera tion of the APN service will be rejected(传输病毒,文件,代码或程序,导致破坏或扰乱正常的APN服务操作的应用会被拒。)

 

6. Game Center(游戏中心)

6.1
Apps that display any Player ID to end users or any third party will be rejected(向终端用户或第三方展示Player ID的应用会被拒。)

6.2
Apps that use Player IDs for any use other than as approved by the Game Center terms will be rejected(Player ID被用于Game Center条款款意外的用途的应用会被拒。)

6.3
Developers that attempt to reverse lookup, trace, relate, associate, mine, harvest, or otherwise exploit Player IDs, alias, or other information obtained through the Game Center will be removed from the iOS Developer Program(试图通过Game Center反查,跟踪,描述,关联,发掘,收割,或利用Player ID,别名或其他信息的开发者会被取消IDP身份。)

6.4
Game Center information, such as Leaderboard scores, may only be used in Apps approved for use with the Game Center(Game Center信息,例如Leaderboard得分,只能通过Game Center用于应用中。)

6.5
Apps that use Game Center service to send unsolicited messages, or for the purpose of phishing or spamming will be rejected(使用Game Center发送主动消息,欺骗或干扰信息的应用会被拒。)

6.6
Apps that excessively use the network capacity or bandwidth of the Game Center will be rejected(使用Game Center过度占用网络带宽或容量的应用会被拒。)

6.7
Apps that transmit viruses, files, computer code, or programs that may harm or disrupt the normal operation of the Game Center service will be rejected(传输病毒,文件,代码或程序,导致破坏或扰乱正常的Game Center操作的应用会被拒。)

7. Advertising(广告)

7.1
Apps that artificially increase the number of impressions or click-throughs of ads will be rejected(人工刷广告浏览或点击率的应用会被拒。)

7.2
Apps that contain empty iAd banners will be rejected(带有空iAd banner广告的应用会被拒。)

7.3
Apps that are designed predominantly for the display of ads will be rejected(设计主要用来展示广告的应用会被拒。)

8. Trademarks and trade dress(商标权与商标外观)

8.1
Apps must comply with all terms and conditions explained in the Guidelines for Using Apple Trademarks and Copyrights and the Apple Trademark List(应用必须遵守Guidelines for Using Apple Trademarks and Copyrights 和Apple Trademark List中描述的所有条款和条件。)

8.2
Apps that suggest or infer that Apple is a source or supplier of the App, or that Apple endorses any particular representation regarding quality or functionality will be rejected(任何误导或暗示Apple为该应用来源或提供商,或Apple以任何形式认可其质量或功能的应用会被拒。)

8.3
Apps which appear confusingly similar to an existing Apple product or advertising theme will be rejected(外观与现有Apple产品或广告主题类似或混淆的应用会被拒)

8.4
Apps that misspell Apple product names in their App name (i.e., GPS for Iphone, iTunz) will be rejected(应用名称中出现错误的Apple产品拼写(如,GPS for IPhone, iTunz)的应用会被拒。)

8.5
Apps may not use protected third party material such as trademarks, copyrights, patents or violate 3rd party terms of use. Authorization to use such material must be provided upon request.(使用受保护的第三方资源(商标,版权,商业机密,以及其他私有内容),如果要求请提供一份文本形式的使用授权。)

9. Media content(媒体内容)

9.1
Apps that do not use the MediaPlayer framework to access media in the Music Library will be rejected(使用MediaPlayer框架以外的方法访问Music Library中媒体数据的应用会被拒。)

9.2
App user interfaces that mimic any iPod interface will be rejected(用户界面模仿任何iPod界面的应用会被拒。)

9.3
Audio streaming content over a cellular network may not use more than 5MB over 5 minutes(通过蜂窝网络传输的流媒体音频内容不得超过5MB或多余5分钟。)

9.4
Video streaming content over a cellular network longer than 10 minutes must use HTTP Live Streaming and include a baseline 64 kbps audio-only HTTP Live stream(通过蜂窝网络传输超过10分钟流媒体视频内容,必须使用HTTP Live Streaming,并包含一条基线64kbps的音频HTTP Live流。)

10. User interface(用户界面)

10.1
Apps must comply with all terms and conditions explained in the Apple iOS Human Interface Guidelines(应用必须遵守Apple iOS Human Interface Guidelines中的所有条款和条件。)

10.2
Apps that look similar to Apps bundled on the iPhone, including the App Store, iTunes Store, and iBookstore, will be rejected( 外观与iPhone自带应用(如:App Store,iTunes Store和iBookstore)相似的应用会被拒。)

10.3
Apps that do not use system provided items, such as buttons and icons, correctly and as described in the Apple iOS Human Interface Guidelines may be rejected(不按照Apple iOS Human Interface Guidelines中的描述正确使用系统控件比如按钮,图标等的应用会被拒。)

 

10.4
Apps that create alternate desktop/home screen environments or simulate multi-App widget experiences will be rejected(试图创建多桌面/主屏环境或模拟多Widget应用工具的应用会被拒。)

10.5
Apps that alter the functions of standard switches, such as the Volume Up/Down and Ring/Silent switches, will be rejected( 修改标准开关标准功能例如:音量增加/减少,响铃/震动的应用会被拒。)

10.6
Apple and our customers place a high value on simple, refined, creative, well thought through interfaces. They take more work but are worth it. Apple sets a high bar. If your user interface is complex or less than very good, it may be rejected(Apple和我们的用户都界面报以很高期望,希望他设计的超级简洁,精致,充满创造力,深思熟虑。做到这些确实会消耗很多精力,但是值得。Apple在这方面要求非常高。如果你的用户界面过于复杂,甚至仅仅是不够好,都可能被拒。)

11. Purchasing and currencies(购买与流通货币)

11.1
Apps that unlock or enable additional features or functionality with mechanisms other than the App Store will be rejected(通过App Store以外的渠道解锁或开启附加属性或功能的应用会被拒。)

11.2
Apps utilizing a system other than the In-App Purchase API (IAP) to purchase content, functionality, or services in an App will be rejected( 使用In App Purchase API (IAP)以外的系统提供购买内容,功能或服务的应用会被拒。)

11.3
Apps using IAP to purchase physical goods or goods and services used outside of the application will be rejected(使用IAP为与应用无关的实体商品或商品服务收费的应用会被拒。)

11.4
Apps that use IAP to purchase credits or other currencies must consume those credits within the application(应用使用IAP购买积分(Credit)或其他货币,必须在应用中消费。)

11.5
Apps that use IAP to purchase credits or other currencies that expire will be rejected(使用IAP购买的积分(Credit)或货币会过期的应用会被拒)

11.6
Content subscriptions using IAP must last a minimum of 7 days and be available to the user from all of their iOS devices( 使用IAP收费订阅的内容至少要在7天内有效,而且允许在所有iOS设备间共享。)

11.7
Apps that use IAP to purchase items must assign the correct Purchasability type(用到IAP收费项目的应用必须分派到正确的收费类目中。)

11.8
Apps that use IAP to purchase access to built-in capabilities provided by iOS, such as the camera or the gyroscope, will be rejected( 使用IAP向用户收费以获取iOS内建功能(如摄像头,陀螺仪)的应用会被拒。)

11.9
Apps containing “rental” content or services that expire after a limited time will be rejected( 包含“出租”内容或服务的应用,在一段时间实效的会被拒。)

11.10
Insurance applications must be free, in legal-compliance in the regions distributed, and cannot use IAP(保险类应用必须免费,遵守发布地区的法律,并且不允许使用IAP。)

11.11
In general, the more expensive your App, the more thoroughly we will review it(一般来说,越贵的应用审核就越仔细彻底。)

11.12
Apps offering subscriptions must do so using IAP, Apple will share the same 70/30 revenue split with developers for these purchases, as set forth in the Developer Program License Agreement.(提供收费订阅的应用必须使用IAP,Apple将会按照Developer Program License Agreement中约定的70/30的比例与开发者分账。)

11.13
Apps that link to external mechanisms for purchases or subscriptions to be used in the App, such as a “buy” button that goes to a web site to purchase a digital book, will be rejected(应用中如果提供了IAP以外的收费或订阅机制,如:“buy”按钮,跳转到一个购买电子书的web页面,会被拒。)

11.14
Apps can read or play approved content (specifically magazines, newspapers, books, audio, music, and video) that is subscribed to or purchased outside of the App, as long as there is no button or external link in the App to purchase the approved content. Apple will not receive any portion of the revenues for approved content that is subscribed to or purchased outside of the App( 应用可以阅读或播放任何在应用以外取得授权的内容(包括指定的杂志,报纸,书籍,音频,音乐和视频),只要在应用中不允许出现获取授权的收费链接或按钮。Apple不会对在应用外订阅或购买授权项目收取任何费用。)

11.15
Apps may only use auto renewing subscriptions for periodicals (newspapers, magazines), business Apps (enterprise, productivity, professional creative, cloud storage) and media Apps (video, audio, voice), or the App will be rejected.(应用只能自动更新订阅的期刊(报纸、杂志),自动更新商业应用(企业、生产力、专业创意、云存储)和媒体应用(视频、音频,声音)将被拒*。)

12. Scraping and aggregation(抓去与整合)

12.1
Applications that scrape any information from Apple sites (for example from apple.com, iTunes Store, App Store, iTunes Connect, Apple Developer Programs, etc) or create rankings using content from Apple sites and services will be rejected(从Apple的页面(如:apple.com, iTunes Store, App Store, iTunes Connect, Apple Developer Programs, 等)抓取内容,或利用Apple页面和服务中的内容进行排名的应用会被拒。)

12.2
Applications may use approved Apple RSS feeds such as the iTunes Store RSS feed( 应用可以使用授权的Apple RSS,例如iTunes Store RSS。)

 

12.3
Apps that are simply web clippings, content aggregators, or a collection of links, may be rejected(简单的web页面裁剪,内容整合或链接收集应用会被拒。)

13. Damage to device(损害设备)

13.1
Apps that encourage users to use an Apple Device in a way that may cause damage to the device will be rejected(任何怂恿用户做出可能损坏Apple设备的行为的应用会被拒。)

13.2
Apps that rapidly drain the device’s battery or generate excessive heat will be rejected(快速耗光设备电量或产生大量热量的应用会被拒。)

14. Personal attacks(人身攻击)

14.1
Any App that is defamatory, offensive, mean-spirited, or likely to place the targeted individual or group in harms way will be rejected( 任何涉嫌诽谤,侮辱,狭隘内容或打击个人或团体的应用会被拒。)

14.2
Professional political satirists and humorists are exempt from the ban on offensive or mean-spirited commentary(职业政治讽刺家和幽默作家不受该诽谤和狭隘条款约束。)

15. Violence(暴力)

15.1
Apps portraying realistic images of people or animals being killed or maimed, shot, stabbed, tortured or injured will be rejected(展示人或动物被杀戮,致残,枪击,针刺或其他伤害的真实图片的应用会被拒)

15.2
Apps that depict violence or abuse of children will be rejected(描述暴力或虐待儿童的应用会被拒。)

15.3
“Enemies” within the context of a game cannot solely target a specific race, culture, a real government or corporation, or any other real entity(游戏中的“敌人”不能单独的设定为某特定比赛,文化,真实的政府或组织,或者任何现实事物。)

15.4
Apps involving realistic depictions of weapons in such a way as to encourage illegal or reckless use of such weapons will be rejected(含有以鼓励非法或鲁莽使用的方式描述真实武器的应用会被拒。)

15.5
Apps that include games of Russian roulette will be rejected( 带有俄罗斯轮盘游戏的应用会被拒。)

16. Objectionable content(负面内容)

16.1
Apps that present excessively objectionable or crude content will be rejected(介绍过度三俗和粗鲁内容的应用会被拒。)

16.2
Apps that are primarily designed to upset or disgust users will be rejected(设计来惹怒或恶心用户的应用会被拒。)

17. Privacy(隐私)

17.1
Apps cannot transmit data about a user without obtaining the user’s prior permission and providing the user with access to information about how and where the data will be used(在未获得用户事先允许,或未告知用户信息将被如何,在哪里使用的情况下,应用不可以传输用户数据。)

17.2
Apps that require users to share personal information, such as email address and date of birth, in order to function will be rejected(要求用户提供个人信息,如邮箱地址,生日等,才能使用其功能的应用会被拒。)

17.3
Apps that target minors for data collection will be rejected(专门收集未成年人数据的应用会被拒。)

18. Pornography(色情)

18.1
Apps containing pornographic material, defined by Webster’s Dictionary as “explicit descriptions or displays of sexual organs or activities intended to stimulate erotic rather than aesthetic or emotional feelings”, will be rejected(含有韦氏词典中定义的色情素材(explicit descriptions or displays of sexual organs or activities intended to stimulate erotic rather than aesthetic or emotional feelings)的应用会被拒。)

18.2
Apps that contain user generated content that is frequently pornographic (ex “Chat Roulette” Apps) will be rejected(经常有用户提供色情内容(例如:Chat Roulette http://en.wikipedia.org/wiki/Chatroulette )的应用会被拒。)

19. Religion, culture, and ethnicity(信仰,文化和种族)

19.1
Apps containing references or commentary about a religious, cultural or ethnic group that are defamatory, offensive, mean-spirited or likely to expose the targeted group to harm or violence will be rejected(带有对一种信仰,文化或种族进行诽谤,侮辱,狭隘,或以他们为目标的暴力或伤害内容的应用会被拒。)

19.2
Apps may contain or quote religious text provided the quotes or translations are accurate and not misleading. Commentary should be educational or informative rather than inflammatory( 应用若带有或应用对一种信仰的文字描述,那么这个引用或翻译必须是精确,无歧义的。注释内容可以具有教育性,信息性,但不可以为煽动性。)

20. Contests, sweepstakes, lotteries, and raffles(竞赛,赌博,彩票和抽*)

20.1
Sweepstakes and contests must be sponsored by the developer/company of the App(赌博和竞赛必须是由应用开发者或所有公司发起资助的。)

20.2
Official rules for sweepstakes and contests, must be presented in the App and make it clear that Apple is not a sponsor or involved in the activity in any manner( 应用中必须展示赌博和竞赛的官方条款,并声明Apple不是资助者,并且在任何情况下与此事无关。)

20.3
It must be permissible by law for the developer to run a lottery App, and a lottery App must have all of the following characteristics: consideration, chance, and a prize(开发者必须经过法律允许才能上线一款抽*应用,而且抽*应用必须具备以下要素:报酬,机会,和*金。)

 

20.4
Apps that allow a user to directly purchase a lottery or raffle ticket in the App will be rejected(直接允许用户在应用中购买彩票或抽*的应用会被拒。)

21. Charities and contributions(慈善与捐助)

21.1
Apps that include the ability to make donations to recognized charitable organizations must be free(含有向已认证的慈善机构捐助功能的应用必须是免费的。)

21.2
The collection of donations must be done via a web site in Safari or an SMS(慈善募捐必须通过短信息或通过Safari访问web页面完成。)

22. Legal requirements(法律要求)

22.1
Apps must comply with all legal requirements in any location where they are made available to users. It is the developer’s obligation to understand and conform to all local laws(应用必须遵守所有发布地区当地法律。开发者有义务了解和遵守各地的法律。)

22.2
Apps that contain false, fraudulent or misleading representations or use names or icons similar to other Apps will be rejected( 任何带有虚假,欺诈和带有歧义的内容的应用会被拒。)

22.3
Apps that solicit, promote, or encourage criminal or clearly reckless behavior will be rejected( 任何召集,推销和股东犯罪和鲁莽行为的应用会被拒。)

22.4
Apps that enable illegal file sharing will be rejected(非法文件共享应用会被拒。)

22.5
Apps that are designed for use as illegal gambling aids, including card counters, will be rejected(任何设计用来非法赌博工具,包括算牌的应用会被拒。)

22.6
Apps that enable anonymous or prank phone calls or SMS/MMS messaging will be rejected( 提供知识拨打电话或知识发送短消息/彩信功能的应用会被拒。)

22.7
Developers who create Apps that surreptitiously attempt to discover user passwords or other private user data will be removed from the iOS Developer Program(任何开发暗中获取用户密码和私有数据的开发者会被取消IDP身份。)

22.8
Apps which contain DUI checkpoints that are not published by law enforcement agencies, or encourage and enable drunk driving, will be rejected(任何非法律执行部门发布的带有DUI检查点信息,或鼓励且协助酒后驾车的应用会被拒。)

自学 iOS 开发的一些经验

不知不觉作为 iOS 开发也有两年多的时间了,记得当初看到 OC 的语法时,愣是被吓了回去,隔了好久才重新耐下心去啃一啃。啃了一阵,觉得大概有了点概念,看到 Cocoa 那么多的 Class,又懵了,怎么才能调用系统的相机?怎么保存信息?怎么做一个像 Twitter 个人页那样的页面?总之就是不知道该从哪切入。

现在回想起来,其实路一直都在,而且有很多条,当初如果有人能够指出一条还不错的道,或许就能走得不那么艰难。于是就有了这篇文章,希望对后人能有所帮助吧。

基础

一定的编程经验

这里说的编程经验是至少熟练一门编程语言,对 OOP 有一定的了解,*好熟悉一些基本的设计模式。遇到过的好多 iOS 开发,大多是从别的语言转过来的,所以有一定的编程基础,学起来会更容易 get the point.

如果是*次接触编程,当然也是没问题的,只是要做好心理准备,可能会比想象的难。

英语

发现不少开发对于英语似乎有点接受不能,通常都是中文优先,除非迫不得已,才硬着头皮看看 StackOverflow,英文文章,文档等。忘了是谁说过「难走的路越走越好走」,通常如此。其实只要稍微 push 一下自己,那些技术文章啃下来应该不会有太大的问题,有过几次成功的体验后,这种恐惧感就会减少很多。优质的文章、视频、书籍,多是英文的,不迈过这个 坎,将来要么成为瓶颈,要么花更大的成本去填补。

入门

书籍

要学习 iOS 开发,自然要先学 Objective-C (当然现在也可以直接上 Swift,不过如果多人协作的话,OC目前还是主流),因为 OC 是 C 语言的超集,所以了解 C 语言对于学习 OC 肯定会有帮助,不过就算不了解,直接学 OC 也没太大问题。

这里推荐 BNR (Big Nerd Ranch) 的这本 Objective-C Programming The Big Nerd Ranch Guide,讲解地比较细致,能帮助你更好的理解 OC,更重要的是教你遇到问题时,如何去解决问题,以及这个问题对应的一些知识点,如何使用文档等等。

来到一个新的世界,肯定会对这个世界充满好奇,想订阅一大堆博客,买一堆书,看各种教程和视频,然后就变得浮躁,不知该从哪下手,这会导致拖延症。 我渴了,给我倒一杯水,这个很直接,马上就可以做,但如果是给我买一瓶饮料,而自己对那些饮料又不怎么熟悉时,就纠结了,不如刷会微博,看看朋友圈,玩个小游戏先。

所以一本好的入门教材很重要,要契合自己当前的水平,且常常会有收获,这种成就感会激励着你继续学下去。

在看书的过程中,往往会有这样的经历:书中提到某个人、观点、知识点、书、文章,然后就顺着它提到的这些东西出去了,可能某个知识点又牵扯到另一些内容,然后就这样越走越远。想起了一个故事——

三只猎狗追一只土拔鼠,土拔鼠逃跑时钻进了一个树洞。这个树洞只有一个出口,不一会儿,忽然从树洞里跑出一只兔子。兔子飞快地向前跑,并爬上另一棵大树。兔子因为慌乱在树上没站稳,掉了下来,砸晕了正仰头看的三只猎狗,*后,兔子终于逃脱。

对于这个故事可以从不同的角度去解读,我更愿意以初心去解读。兔子为什么会爬树?为什么能砸晕三只猎狗?这不是重点,重点是,之前追赶的土拨鼠哪去了?看书时难免会有延伸阅读,这个深度我觉得不宜超过 2 层,不然很容易就回不来了。

还有就是如果有可能,*好每天都看点,这其实是很难的,因为总是会有优先级更高的事,或者之前的某些习惯在干扰。一旦断了几天,就不想再拿起来了。

还有,苹果官方的 Start Developing iOS Apps Today 也是很不错的入门材料。

视频

推荐斯坦福老头子(Paul Hegarty)的 Developing iOS 7 Apps for iPhone and iPad ,当初也是看的这个(那时还是更老的版本),Paul 是资深的 Mac/iOS 开发(前苹果员工?),很多知识点讲得很到位,学生们的提问也大都在点上,同时配有Demo,总之听下来会对 iOS 开发有比较全面的了解。

同时推荐一本小册子:objc-zen-book,花不长时间就能看完,里面是一些 Best Practices,对于编写优质代码会很有帮助。

笔记

这是一个持久的过程,任何阶段都适用。以前也没太在意这个,觉得概念性的东西,脑子过一遍,就大概知道了,然后就去啃其他的东西了,现在看来,如果有记笔记的话,会更有助于消化概念、知识点,也可以记录自己的思考过程。达芬奇就记录了10000多页的笔记。

记笔记可以加深对知识点的理解,而成为编程巨星的唯一秘诀就是:对所做的事情理解地越深,就会做得越好。同时如果遵循遗忘曲线去复习的话,效果更佳。对知识点了解地足够透彻后,Debug 时才更有可能知道问题出在哪,解决问题也更容易有思路。

笔记不仅可以记知识点,也可以记录调试过程,比如这篇笔记,有一种调试方法:小黄鸭调试法

许多程序员都有过向别人(甚至可能向完全不会编程的人)提问及解释编程问题,就在解释的过程中击中了问题的解决方案。一边阐述代码的意图一边观察它实际上的意图并做调试,这两者之间的任何不协调会变得很明显,并且更容易发现自己的错误。

生活中我们可能不会真的这么去做,这时抽离出另一个自己,记录下跟ta的对话,也是个发现问题的好方法。

练习

这也是一个持续的过程,知道了些概念或原理后,总是会想着去验证下是不是这样,无论结果是否如自己预期,实践的过程会降低对语言的陌生感,慢慢地培养一种驾驭这门语言的自信,如果出了错,正好可以重新梳理一下。

目标

如果静下心来看完了 BNR 的这本书,以及斯坦福的 iOS 开发视频,那么对 OC 应该比较了解了,一些常用的 UIKit 用起来也没什么问题了,比如 UIViewController / UIView / UIScrollView / UIImageView / UITableView。也熟悉一些概念,如 KVO / MVC / Delegate / DataSource。

这个阶段下来,应该会有:哦,iOS 开发也就这样嘛,多翻翻文档,熟悉 Cocoa Touch 的一些 Class,差不多也能做出一个简单的 App 了。

进阶

入门之后,接下来可以折腾的东西还会有不少。

书籍

Effective Objective-C 2.0,里面提到了 52 种提高 iOS App 质量的途径。涉及了 API 设计、protocols / category 的使用、写出更模块化的代码等,读下来应该会有不少收获。

iOS Programming: The Big Nerd Ranch Guide (4th Edition),又是一本 BNR 的书,这本书的特点是通过 Demo 来引出知识点,然后提一些问题,并且会细说解题思路。看书的过程中,对于元学习能力的提升也会有一定帮助。

— update —

发现巧哥的 iOS开发进阶 已经可以在京东买到了,虽然没有细看,但巧哥出品质量肯定有保障。

其他资源

进入这个阶段后,可以去探索更大的世界了,现在的资源已经很丰富了,但还是要遵循「少而精」的原则。以下是我觉得挺不错的资源

iOS Dev Weekly 每周一期,内容多为这一星期里值得关注的Github项目、文章、工具等。

iOS 移动开发周报 这是唐巧大大整理的每周不错的 iOS 开发相关的内容,多为中文。

RayWenderlich 很多详细又全面的教程,不容错过。

iOS Dev Slack 国内不少 iOS 开发(包括大大们)都在这里,不过现在好像不怎么能拿到邀请了。

中文 iOS/Mac 开发博客列表,打开工具订阅吧。

还有,如果可能的话,多去分享自己学到的东西,教是*好的学,我试过几次,效果真的很不错。

目标

这个阶段下来,对于常用的设计模式、内存管理、Blocks 的使用、图像操作、网络请求和管理、多线程应该比较熟悉了。对于 CALayer、Animation、UIScrollView、UITableView、UICollectionView、 ViewController Container 则非常熟悉,对「非常熟悉」的定义是:不打开 Xcode,脑子里就能把相应的知识点复述出来 80% ,比如这个类有哪些方法,Delegate / DataSource 有哪些方法,怎么使用,如果要实现某个效果,应该怎么做(好吧, UICollectionView 除外)。

高级

其实高级、进阶、入门并没有严格的界限,在入门阶段也可以探究高级阶段的一些东西。我觉得支撑我们不断探索和前进的动力不是兴趣,而是永不满足的好奇心,和对优雅代码的追求。

If your standards are low, you’re going to stop pretty early on in the process.

BNR 的这篇 Leveling Up 已经讲得很好了,也更加细致。

书籍

iOS 7 Programming Pushing the Limits 这本书对 iOS 7 的一些特性会讲解地比较深入,当然也不仅仅是 iOS 7。只叹 iOS 更新实在太快,书籍往往跟不上,一本好书往往需要很长时间来撰写,等书可以出版了,iOS 又出新版本了。

源码

看优秀的源码,可以学到很多东西,使用过程中遇到问题也更容易解决。这些是我觉得值得细看的源码:AFNetworking(NSOperation, HTTP, Block), SDWebImage(Image Handle, Cache, NSOperation, Block),SVPullToRefresh(UIScrollView, State Handle), JSONModel(runtime)

如果有兴趣,也可以翻翻 CoreFoundation / OC runtime 的源码。

资源

oleb

NSHipster

objc.io || objcio.cn

WWDC 视频

工具

chisel Facebook 出品的 LLDB 助手,用于调试很方便

Reveal 每当好奇某个 App 的实现时,都会打开它一窥究竟,用于调试自己的 App 也很方便

Aspects steipete 大大出品的一款方便使用 method swizzling 的工具,可以在运行时动态添加代码到某个方法

class-dump 从 Mach-O 文件生成 OC 头文件,有时想看看某个 App 大概是如何组织的会比较方便

Hopper 可以对二进制文件进行反编译,甚至可以生成伪代码!有时想看看 UIViewController 里某个方法大概是怎么实现的,就可以用它。

Instruments 这个内置的工具对于发现 App 的各种问题很有帮助,如内存占用、泄露,渲染问题等。

目标

这个阶段,对于底层的实现会有更深入的了解,各种 Core 开头的 Framework 至少可以说出个大概,工具也能熟练使用,「正经的代码」写过数万行,可能天天在翻 Dash。如果别人让你实现某个功能,能在较短的时间内给出不错的实现方案,并且足够细致,甚至精细到如何使用 Core Graphic 去画某个图像。

其他

我觉得无论学习什么,「速成」的心态是*要不得的,这只会让自己变得浮躁,一知半解,整个过程也很难让自己的元学习能力得到提升。慢慢来,攻占一个城后,再去打下一个,这时心态也会平和许多。

iOS App上架流程

一、前言:
作为一名iOS开发者,把开发出来的App上传到App Store是必须的。下面就来详细介绍一下具体流程。

二、准备:
一个已付费的开发者账号(账号类型分为个人(Individual)、公司(Company)、企业(Enterprise)、高校(University)四种类型,每年资费分别为$99、$99、$299、免费。)。
一个已经开发完成的项目。
三、检查:
你的Xcode必须是正式版的,beta版本的Xcode是不能上传项目的。
请确认你安装的Xcode是从App Store或者是开发者网站下载的,而不是从其它渠道获取的安装包安装的,因为非官方途径下载的Xcode可能带有XcodeGhost 病毒。如何检查?

检查方法

四、生成发布证书
打开苹果开发者中心:https://developer.apple.com
打开后点击:Member Center

1.苹果开发者中心

下面输入已付款过的Apple账号和密码登录(如果你的电脑已经保存了密码,会直接进入)

开发者登录账号
2.点击:Certificates, Identifiers & Profiles (专门生成证书,绑定Bundle Id,绑定device设备,生成描述文件的地方)

Member Center
3.点击Certificates生成证书

(1)选择iOS, tvOS, watchOS
(2)选择All
(3)点击右上角新添加证书

添加新证书1
(4)由于是做App上传,选择生产证书(选择App Store and Ad Hoc)

选择App Store and Ad Hoc

注意:一个开发者账号只能创建(1-2个开发(测试)证书,2-3个生产(发布)证书),如果你的App Store Ad Hoc 前面的按钮不能选择,则代表你的这个账号无法再创建新的生产证书了。
解决方法:

从共同使用这个账号的人电脑上生成.p12文件,导入自己的电脑。(尽量不要执行下面第2步)
如果你想生成的话,把现有的删除一个(建议删除时间比较靠前的)。注意:如果删除一个证书,那么正在使用这个证书的人将不能再使用了,除非重新生成,然后利用.p12重新导入自己的电脑里!
注意:如果你想删除证书,执行下面步骤,否则略过。

删除证书
然后接上上图,生产证书部分继续

生成证书2

生成证书3

生成证书4
5.上传CSR文件去获取证书(CSR文件需要我们到本机钥匙串里去创建)

(1)在Launchpad的其他里面,点击钥匙串访问弹出如下界面

其他

钥匙串访问
(2)工具栏选择钥匙串访问->证书助理->从证书颁发机构请求证书…

请求证书

证书信息
(3)将CSR文件保存到MAC磁盘的某个位置(这里我选择的是桌面,进行存储)

存储证书

点击完成

CertificateSigningRequest.certSigningRequest 文件
6.然后回到浏览器,点击choose File..

选择CSR文件
7.选择创建好的:CertificateSigningRequest.certSigningRequest 文件,点击选取

选取CSR文件

点击Generate上传证书

上传CSR证书
8.跳转到如下界面,点击 DownLoad 下载生成的证书(cer后缀的文件),然后点击Done,你创建的发布证书就会存储在帐号中。

下载生成的证书

cer后缀的文件
注意:这个证书只能下载一次。点击下载后,关闭页面后就不能再回到下载页面了。
如果不需要给别的电脑使用,则直接跳过下面附加项,跳转到第五步(绑定Bundle Identifier)

附加项:生成p12文件在其他电脑上使用这个发布证书
双击安装证书后,打开钥匙串访问,选择安装的证书右键单击

1.安装的发布证书

注意:如果没有导出,可以把这个证书删除,然后重新双击下载的证书文件安装。
导出证书

导出证书
2.存储证书

存储导出的证书

注意:存储的文件格式一定要是.p12
3.设置密码
可以为证书设置密码,也可以不设置密码;如果设置了密码,那么别人安装这个证书的时候就要输入密码,否则无法安装。这里就不设置密码了。

设置密码
4.保存导出的证书

p12发布证书

如果需要在其它电脑上也能发布App,那么就必须要安装这个发布证书。
五、创建App IDs和绑定你的App的Bundle Identifier
回到刚才的页面:https://developer.apple.com/account/ios/identifiers/bundle/bundleList.action

1.点击App IDs,进入如下界面,点击右上角的 + 号

2.点击App IDs
填写App IDs和Bundle Identifier

填写App IDs和Bundle Identifier

注意:

1.上传App使用的Bundle Identifier(不要有-,都是英文+数字)必须是固定的,不能使用占位符。
2.如果你的Bundle Identifier已经在网站上绑定了,如果你又修改了你工程里面这个Bundle Identifier的话,需要重新进入到开发者账号里面绑定。

修改工程的Bundle Identifier

下面选择App中包含的服务,默认有两项,其余的根据自己项目的需求进行选择

App Services
3.点击continue

4.点击Register

5.点击Done

六、生成描述文件(描述文件的作用就是把证书和Bundle Identifier关联起来)
1.找到Provisioning Profiles ,点击All,然后点击右上角 + 号

2.Provisioning Profiles
因为是发布,所以选择下面App Store这个描述文件,点击Continue

选择App Store
3.在App ID 这个选项栏里面找到你刚刚创建的:App IDs(Bundle Identifier) 类型的套装,点击Continue

选择发布项目的Bundle Identifier
4.选择你刚创建的发布证书(或者生成p12文件的那个发布证书),根据自己电脑上的发布证书日期来选择,点击Continue

选择创建的发布证书
5.在Profile Name栏里输入一个名字(这个是PP文件的名字,可随便输入,在这里我用工程名字,便于分别),然后点击Generate

给描述文件起个名字
6.Download生成的PP文件,然后点击Done,双击安装(闪一下就完事了,没其它效果)

Download生成的PP文件

生成的描述文件
六、在App Store开辟空间
1.回到Member Center,点击iTunes Connect

点击iTunes Connect
2.登录开发者账号(还是之前已付费的账号)

Snip20160315_68.png
3.登录成功后,点击我的App

点击我的App
4.点击左上角那个+号,点击新建(注意:我们是iOS App开发,不要选Mac App啦)

新建 App
5.依次按提示填入对应信息(SKU是公司用于做统计数据之类的id,根据公司需求填写),然后点击创建

填写App信息

注意:如果都填好以后,可能会告诉你,你的App名称已经被占用,那么不好意思,你只能改名了!(而且建议大家起名不要往比较出名的App上靠,否则审核可能会被拒*)

6.填写App其它信息

App信息
7.填写价格和销售范围(由于我的开发者账号没有签订纳税合同,所以不能上线收费应用,所以只能暂时免费)

填写价格和销售范围
8.依次把不同尺寸的App截图拉入到对应的里面
需要填写不同尺寸的手机屏幕截图(也就是拿不同尺寸的模拟器运行后,挑出至少3页*多5页进行截图然后拖到响应的区里)(在模拟器Command+S 就可以保存屏幕截图到桌面了)(注意:如果提示拖进去的图片尺寸不对,则把模拟器弄成100%然后再Command 加 S) 尺寸参照表在下面

设置不同尺寸的App截图

尺寸参照图

9.填写App简介

10.按提示依次输入

错误提示:如果上传App 图标失败,提示Alpha错误的话,看下面。
打开你的图标图片,勾掉这个

勾掉这个
11.点击分级后面的编辑,如实填写后,点击完成

分级信息
12.填写审核信息

版本发布就是:(然后*下面选择自动发布的话就是如果审核通过,就自动上传到App Store供人下载)

13.此时这个构建版本还没有生成,我们先把基本信息填写完毕,然后再进入Xcode中把项目打包发送到过来。
注意:填写完一定要点击右上角的保存。

七、在Xcode中打包工程
找到你刚刚下载的发布证书(后缀为.cer)或者p12文件,和PP文件,双击,看起来没反应,但是他们已经加入到你的钥匙串中。

1.在Xcode中选择iOS Device(这里不能选择模拟器),按照下图提示操作

选择iOS Device
2.如果你的应用不支持横屏,把这两个勾去掉

3.查看版本号和构建版本号

4.配置发布证书

注意:如果这里没有黄色叹号,代表你的配置没问题,如果有,那就是证书和描述文件不匹配,或者描述文件里刚才选的Bundle和现在的工程的Bundle Identifier不一致,去develop.apple.com 上找到你的描述文件在确认下绑定的bundle Identifier和你工程是不是一样的?

检查
5.将断点、全局断点,僵尸模式等都要去掉。

6.设置Release模式(Debug是测试的,Release是发布用的)

7.选择 Xcode下 Product 下 Archive(专门用于传项目,或者打包项目)

选择Archive

8.出现下图说明你没有添加开发者账号,点击右下脚Add… 按钮就可以添加

没有添加开发者账号
9.输入付费的开发者账号

输入开发者账号

可能会弹出下面这个界面,如果不弹出,按Command加。

10.然后回到Archive(选择已付费的账号),然后点击Choose

选择已付费的账号

然后等待

等待
11.选择Upload提交

Upload提交
12.如下就代表上传成功,如果出错,请参照iOS App上传项目遇到的问题

上传成功
13.返回ItunesConnect网站上你自己的App信息中查看一下

14.在这个构建版本这里就可以添加代码

点击+号之后选择代码版本

添加构建版本
15.提交以供审核

16.App已经从准备提交,变成正在等待审核状态

正在等待审核状态

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