转自cocoachina:http://www.cocoachina.com/bbs/read.php?tid=73570&page=15
iPad软件界面设计基本规范
iPad平台特点
1 大屏幕,分辨率 1024x768
2 再次强调没有固定的方向,必须四个方向都支持。
3 可以外接键盘
4 可以放置设备,与台式机同步
iPhone与iPad的共同特点
- 内存有限
- 同时运行单个程序
- 在设置功能里放置程序选项
- 设备方向可以改变
- 最小化的帮助,让用户直接就能看懂
- 程序响应手势而不是鼠标
- 运行源生程序、Web程序或者二者兼备
- 图片深度24位(RGB各8位),另带8位alpha通道,建议使用PNG格式图片
界面简单建议
- 支持所有方向
注意要为每个方向都提供一个启动图片
- 增强交互而非只增加功能
- 使用split view, 导航和弹出窗口来平整信息层级
- 减少全屏切换,仅仅改变需要改变的部分
- 支持协作和互联
- 如果可能,增加真实感以及物理维度到应用程序里,尽可能像真实世界的物品以便用户易于上手
- 界面漂亮 用户喜爱
- 尽量让用户更关注内容而不是界面
- 尽可能减少强制用户模式化操作
- 减少使用列表,改变为其他形式
- 尽量支持多手指手势
- 让用户模式化操作的地方可以用弹出窗口
- 限制复杂的模式化操作
- 减小文件操作尤其尽量让用户感觉不到文件系统的存在
- 仅仅在必须的情况才提示用户保存
- 将工具条整合在界面上部
- 尽可能快速启动程序
尽量使用截图作为启动画面
不要在启动时显示关于或者splash screen
恢复上次运行状态
尽量不要让用户提供设置信息
- 程序可能随时停止
- 为每个方向都准备启动画面
- 建立漂亮的图标
尺寸:72x72
和iPhone程序类似,iPad程序的图标会自动增加:圆角、阴影和高光。
图标标准:
> 90度直角边
> 没有高光
> 不带alpha通道透明
- 遵循已有规范
自定义View的常用方法:
onFinishInflate() 当View中所有的子控件均被映射成xml后触发
onMeasure(int, int) 确定所有子元素的大小
onLayout(boolean, int, int, int, int) 当View分配所有的子元素的大小和位置时触发
onSizeChanged(int, int, int, int) 当view的大小发生变化时触发
onDraw(Canvas) view渲染内容的细节
onKeyDown(int, KeyEvent) 有按键按下后触发
onKeyUp(int, KeyEvent) 有按键按下后弹起时触发
onTrackballEvent(MotionEvent) 轨迹球事件
onTouchEvent(MotionEvent) 触屏事件
onFocusChanged(boolean, int, Rect) 当View获取或失去焦点时触发
onWindowFocusChanged(boolean) 当窗口包含的view获取或失去焦点时触发
onAttachedToWindow() 当view被附着到一个窗口时触发
onDetachedFromWindow() 当view离开附着的窗口时触发,Android123提示该方法和 onAttachedToWindow() 是相反的。
onWindowVisibilityChanged(int) 当窗口中包含的可见的view发生变化时触发
public class cwjView extends View { public cwjView(Context context) { super(context); setFocusable(true); //允许获得焦点 setFocusableInTouchMode(true); //获取焦点时允许触控 } @Override protected Parcelable onSaveInstanceState() { //处理窗口保存事件 Parcelable pSaved = super.onSaveInstanceState(); Bundle bundle = new Bundle(); //dosomething return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { //处理窗口还原事件 Bundle bundle = (Bundle) state; //dosomething super.onRestoreInstanceState(bundle.getParcelable("cwj")); return; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) //处理窗口大小变化事件 { super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //如果不让父类处理记住调用setMeasuredDimension } @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { super.onLayout (changed,left,top, ight,bottom) ; } @Override protected void onDraw(Canvas canvas) { Paint bg = new Paint(); bg.setColor(Color.Red); canvas.drawRect(0, 0, getWidth()/2, getHeight()/2, bg); //将view的左上角四分之一填充为红色 } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); //让父类处理屏幕触控事件 } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //处理按键事件,响应的轨迹球事件为 public boolean onTrackballEvent (MotionEvent event) switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: break; case KeyEvent.KEYCODE_DPAD_DOWN: break; case KeyEvent.KEYCODE_DPAD_LEFT: break; case KeyEvent.KEYCODE_DPAD_RIGHT: break; case KeyEvent.KEYCODE_DPAD_CENTER: //处理中键按下 break; default: return super.onKeyDown(keyCode, event); } return true; } } public class cwjView extends View { public cwjView(Context context) { super(context); setFocusable(true); //允许获得焦点 setFocusableInTouchMode(true); //获取焦点时允许触控 } @Override protected Parcelable onSaveInstanceState() { //处理窗口保存事件 Parcelable pSaved = super.onSaveInstanceState(); Bundle bundle = new Bundle(); //dosomething return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { //处理窗口还原事件 Bundle bundle = (Bundle) state; //dosomething super.onRestoreInstanceState(bundle.getParcelable("cwj")); return; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) //处理窗口大小变化事件 { super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //如果不让父类处理记住调用setMeasuredDimension } @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { super.onLayout (changed,left,top, ight,bottom) ; } @Override protected void onDraw(Canvas canvas) { Paint bg = new Paint(); bg.setColor(Color.Red); canvas.drawRect(0, 0, getWidth()/2, getHeight()/2, bg); //将view的左上角四分之一填充为红色 } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); //让父类处理屏幕触控事件 } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //处理按键事件,响应的轨迹球事件为 public boolean onTrackballEvent (MotionEvent event) switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: break; case KeyEvent.KEYCODE_DPAD_DOWN: break; case KeyEvent.KEYCODE_DPAD_LEFT: break; case KeyEvent.KEYCODE_DPAD_RIGHT: break; case KeyEvent.KEYCODE_DPAD_CENTER: //处理中键按下 break; default: return super.onKeyDown(keyCode, event); } return true; } }
以上是View实现的一些基本接口的回调方法,一般我们需要处理画布的显示时,重写onDraw(Canvas)用的的是最多的:
@Override protected void onDraw(Canvas canvas) { //这里我们直接使用canvas对象处理当前的画布,比如说使用Paint来选择要填充的颜色 Paint paintBackground = new Paint(); paintBackground.setColor(getResources().getColor(R.color.xxx)); //从Res中找到名为xxx的color颜色定义 canvas.drawRect(0, 0, getWidth(), getHeight(), paintBackground); //设置当前画布的背景颜色为paintBackground中定义的颜色,以0,0作为为起点,以当前画布的宽度和高度为重点即整块画布来填充,具体的请查看Android123未来讲到的Canvas和Paint,在Canvas中我们可以实现画路径,图形,区域,线。而Paint作为绘画方式的对象可以设置颜色,大小,甚至字体的类型等等。 }
当然还有就是处理窗口还原状态问题(一般用于横竖屏切换),除了在Activity中可以调用外,开发游戏时我们尽量在View中使用类似
@Override protected Parcelable onSaveInstanceState() { Parcelable p = super.onSaveInstanceState(); Bundle bundle = new Bundle(); bundle.putInt("x", pX); bundle.putInt("y", pY); bundle.putParcelable("android123_state", p); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { Bundle bundle = (Bundle) state; dosomething(bundle.getInt("x"), bundle.getInt("y")); //获取刚才存储的x和y信息 super.onRestoreInstanceState(bundle.getParcelable("android123_state")); return; }
原文链接地址:http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial
免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!
本文由yy翻译。Powered by YY!:)
教程截图:
作为一名无证程序员,无论你多么精通Objective-C的内存管理,随着时间的推移,你也不可避免的犯内存相关的错误。但通常因为代码量太大,以至于你不可能一行一行的去排除(等你解决完,你设计的动车早相撞了!)
幸运的是,苹果已经提供了一些好的方式来帮助你找到应用程序中内存相关的问题。有时,这些工具可能吓到初学者,但它们实际上相当有用并易于掌握!
这就是本教程说要介绍的.你会亲手使用内存工具在XCode环境下很轻松的检测内存问题。
这篇教程是建立在你非常熟悉Objective-C内存管理的基础上。如果你还在这个问题上找不着北,你可能需要学习内存管理其他教程。
第一步
在这一节中,我们的目的是在一个例子应用程序中检查、解决任何内存泄漏问题,以演示常见的内存相关错误处理。开始,下载一个应用程序示例。我已经将教程和示例工程文件放在一起了。
在XCode中打开工程并运行。你会看到tableview中包含了一个寿司列表。试着选择几行,然后——轰!你看到可怕的EXC_BAD_ACCESS错误,编译器拿它完全没有办法。
因为xcode完全没指出出问题的地方,所以这种情况通常令许多开发者感到郁闷。当你遇到了一个EXC_BAD_ACCESS错误,我通常会给开发者几个建议:
1.在可执行选项中设置NSZombieEnabled参数,这有时会帮缩小问题的范围;
2.运行apple的内存检测工具,如 Leaks ,以便寻找内存问题;
3设定一个断点,单步运行代码,直到你找到引起崩溃的位置;
4.注释代码,直到不崩溃为止,然后再从后往前查找错误;
现在让我们从第一条开始实验
# 1 - NSZombieEnabled参数
一大波僵尸正在靠近!!!!
不幸的是,NSZombieEnabled选项对于崩溃毫无办法,所以你完全可以放弃抵抗。
当你试图使用一个已经被销毁的对象,NSZombieEnabled会标志一个警告,所以NSZombieEnabled只是一个flag。这是一个良好的开端,因为大多数崩溃的原因都是使用了已经销毁的对象。
按照以下设置:在XCode中展开Executables->双击PropMemFun->选择Arguments选项卡->“Variables to be set in the environment”点击加号按钮。把变量名值设置成NSZombieEnabled,把值设置成YES,如下图:(xcode4在左上角,edit schema里面)
重新运行app,随便操作下使程序崩溃。 查看下console log你就会看到如下信息:
2011-02-03 12:07:44.778 PropMemFun[27224:207] *** -[CFString respondsToSelector:]: message sent to deallocated instance ...
这个程序将在很精确的一行暂停。崩溃后,你可以通过选定第一个区域,回溯找出导致崩溃的准确行数。比如现在这个示例就崩溃在:tableView:didSelectRowAtIndexPath。
不管你信不信,反正找出了出问题的那行。导致崩溃的问题就是向已经销毁的string发送了一个消息。这一行用了两个string: _lastSushiSelected和sushiString.
因为这个string是由stringWithFormat初始化,所以看起来程序是没有问题了,因为stringWithFormat的返回值是自动释放的,所以在下次使用前应该是安全的。但是_lastSushiSelected的安全性如何呢?
虽然_lastSushiSelected是在sushiString执行到最后才赋值的。但是sushiString是自动释放的,所以有些时候sushiString被释放了,内存也被销毁。但是紧接着_lastSushiSelected 仍然有可能指向被销毁的内存!这就解释了崩溃原因:向已经销毁的内存发送消息导致崩溃。
我们只需保留_lastSushiSelected就可以解决这个问题,把最后一行改成下面的样子:
_lastSushiSelected = [sushiString retain];
再次运行程序,你会发现程序已经畅通无阻了。
编译,分析和总结
至少,我们有一个不崩溃的应用程序——这是一个好的开始。但接下来,我们需要开始确保没有任何内存泄漏。
有一种简单的方法可以初步确认你的程序在初始化中是否有任何内存泄漏或其他问题--使用内置编译和分析功能(built-in Build and Analyze)。
这将使XCode执行你的代码和自动检测任何错误并警告你任何潜在的问题。它并不会找出所有的问题,但用这个方法找出的错误无疑是一个既快速又简单的方法。
试一试通过选择Build/Build and Analyze。你应该看到,它检测到一个内存泄漏,你可以看到如下:
消息显示,“alertView”有一个潜在的内存泄漏。如果你看看这一行,你就会发现所有的UIAlertView创造是有着alloc /init (返回一个对象引用数1),却从来没有真正地释放!有几种方法可以解决这个问题,但其中一个方法就是在[alertView show]下面加上一行:
[alertView release];
再次 Build/Build and Analyze,你会发现已经找不出任何内存问题了。
泄漏和管道
不幸的是,你不能依靠Build/Build and Analyze找出一切问题。有一个强大的自动化工具来帮助你检查程序是否有内存泄漏– the Leaks Instrument。
让我们试试看。选择Run/Run -> Performance -> Tool/Leaks,再选择table view中的几行。也可以上下滚动table view,从table view顶端到底部。基于前面的经验,你就应该开始看出一些蓝色的标签出现在泄漏的内存上。
点击停止按钮,然后去工具栏中点击“Leaked Blocks”让他变成“Call Tree”。在面板左下角,点击“Invert Call Tree”、“Hide System Libraries”。你将会看到这个工具发现两个不同的函数存在内存泄漏,你可以看到如下:
如果你双击一个函数的名字,它会带你直接到存在内存泄露的这行代码。这可以给你一个很好的错误位置提示,如果你查看代码并加以思考,你应该能够找出问题所在并解决它。
所以,为什么不看看代码,并且看看你是否能找出问题所在并修正吗?一旦你作出修改,并且能够无错误提示的跑Leaks。如果通过,表示你完成了
…
…waiting…
…
…waiitng…
…
…waiting…
…!
你已经搞定了,不管你信不信,反正我是信了。
解释一下
tableView:didSelectRowAtIndexPath
Leaks 告诉我们,这个问题的原因是字符串sushiString创造和存储过程中引起的内存泄漏。所以让我们一步一步的分析一下原因:
1.当sushiString被创建时,调用stringWithFormat。 返回一个对象数值1并且发送autorelease消息。
2.在方法的最后一行,你在sushiString加入retain(retain数值增加到2)并将其存储到_lastSushiSelected。
3.后来,autorelease生效,retain数递减为1。
4.下一个tableView:didSelectRowAtIndexPath方法被调用,你重写_lastSushiSelected变量的一个指针指向一个新的字符串,- - - - -如果没有释放旧的! 所以那个老字符串并没有被释放仍然存在。
一个解决办法是增加下面一行在初始化lastSushiSelected sushiString之前:
[_lastSushiSelected release];
tableView:cellForRowAtIndexPath
就像在前面的方法,创建和存入名为sushiString的变量引起内存泄漏。以下是引起问题的分析:
1.一个新的字符串被alloc/init方法创建。
2.返回一个对象引用数 1.
3.然而,这个计数从来不减少,所以有一个内存泄漏!
这可以通过三种方式中的一种解决:
1.设置textLable为一个字符串后在sushiString中调用release方法。
2.alloc/init方法初始化完毕后在sushiString中调用autorelease。
3.用stringWithFormat代替alloc/init方法,返回一个已经标志为自动释放的字符串。
验证 leaks!
修正前面提到的问题,再次运行leaks,你会得到一个没有任何内存泄漏的app。
接下来该干什么?
这个链接可以下载到一个已经解决上述问题的工程文件。
最重要的是,你必须亲自实践使用NSZombieEnabled,Build and Analyze,和Leaks Instrument工具来找到内存泄漏。你应该能够很快把这项技术运用到你的工程中。
如果你有更好的方法,可以在下面评论,我也积极采纳大家的建议。