转载自:http://www.raywenderlich.com/zh-hans/18752/%E5%A6%82%E4%BD%95%E5%9C%A8-ios-5-%E4%B8%AD%E4%BD%BF%E7%94%A8-block-1
这篇文章还可以在这里找到 英语
这篇文章来自 iOS 教程团队成员 Adam Burkepile, 一个全职软件咨询顾问和独立 iOS 开发者。 看看他最新的 app Pocket No Agenda , 或者在 Twitter 上面关注它。
Block 是对 C/Objective-C 不可思议的一个扩展。 他能让你把一段代码包装成一个单元并且将他们当做对象一样传送。
iOS 中越来越多的 API 必须要用到 Block。 所以你需要了解它来去做几乎所有的事情。 然而, 它的语法以及一些细节之处经常会让初学者感到迷惑。 不要害怕 – 这正是这篇教程要处理的问题! :]
在这个分为两部分的教程中, 你将要创建一个 iOS 小项目,叫做 iOS Diner。 这个应用本身很简单: 用户从菜单中选择一项来创建订单,好像他们马上就要要吃晚餐。 要注意哦, 这个项目可能也会让你感到饥饿!
在这第一部分的教程里, 我们将会创建这个应用的 UI。 这会是 iOS 5 Storyboard 的一个复习, 还包括了一个关于如何创建一个 web service 用来提供 JSON 格式的晚餐菜单数据的简短教程。
注意: 如果你已经对 Storyboard 和 Interface Builder 很熟悉了, 你可以跳过第一部分,直接去第二部分教程, 那里我们开始讨论如何使用 Block。 这部分主要集中于 Storyboard 和 Interface Builder 的讨论。
教程的第二部分将会大量的使用 Block 来实现应用的逻辑。 它将会展示使用 Block 来进行异步处理, 后台任务, 替换标准的API, 以及更多。
如果你迫不及待的话,那么就持续的阅读吧!
开始
首先,让我们打开 Xcode 并且使用 iOSApplicationSingle View Application 模板来开始一个新项目。
项目名称叫做 “iOSDiner”, 输入用作你创建 App ID 时的 company identifier, 然后输入 “IOD” 作为类名前缀。
设置 device family 为 iPhone。 确保 Use Storyboard 和 Use Automatic Reference Counting 这两个选项是选中的, 但是 Include Unit Tests 和其他的复选框是未选中的。
下一个界面会询问你要把项目存放到哪里。 你可以把它放到任何地方。
运行它一下, 你应该会看到一个空白的屏幕。
在 iPhone/iPod Deployment Info(在 Summary 标签中) 选项中的 Supported Device Orientations 里面, 取消 Portrait 模式, 这样这个应用就只能运行在横屏模式了。
你要做的第一件事就是构建视图界面。 为了完成它,我们需要一些图片。 Ray 的妻子 Vicki 为我们提供了一些漂亮的图片, 你可以到这里下载它。 我们需要将这些图片添加到项目中。
我非常不喜欢 Xcode 在项目和文件系统中匹配文件的方式, 所以大多数时候,我都是手动把资源添加到文件系统中。 我们需要使用 Finder 导航到项目文件夹中。 在项目文件夹中创建一个叫做 “Resources” 的目录。 然后在 Resources 文件夹中创建一个叫做 “Images” 的目录。
把刚下载的 ZIP 文件中的图片复制到 Images 文件夹中, 然后如下图所示,把 Resources 目录拖动到 Xcode 中的 iOSDiner 目录中。
确保下一个对话框中的各项配置和下面的截图是一样的, 然后点击 “Finish” 来添加这些文件。
现在你应该能够看到 Xcode 中的 Resources 目录中有一个 Images子目录, 在它里面包含了你刚刚下载的那些图片 – 完全和文件系统上的结构一样。
添加图片
选中 MainStoryboard.storyboard.
如果你没看到标题为 “View Controller Scene” 的那个界面, 点击最下方的 Expand 按钮 (和下面图片展示的一样)。
你将会为 Storyboard 中的 UIImageView 和 UIButton 添加图片。 为了让一切更简单,展开 Utilities 边栏,并且选择 Media Library。
这里我们看到之前添加到项目中的所有图片。 注意到每张图片都有两个版本。 这是因为每张图片都有默认和视网膜(@2x)版本。
我们仅仅考虑默认版本。 你可以通过选择一张图片并且按下空格键来确定选中的是哪张图片。 名称中不含@2x 的图片就是默认版本。
拖动 “bg_wall.png” 到根视图中并且向下面图示中那样放置它。 如果你不确定图片是否放置正确, 你可以切换到 Size Inspector (Utilities 边栏中的第五个标签),指定精确的 X 和 Y 坐标。
现在对下面这些图片进行同样的操作:
- person.png
- sign_theiOSdiner.png
- chalkboard.png
- bg_counter.png
- total_field.png
- food_box.png
注意,你可以通过选择 Image View 然后找到 EditorSize to Fit Conent(或者使用快捷键 Command-Equals) 属性来让它和它里面的图片尺寸一样。
当你拖动和放置好图片后, 会为每一张图片都创建一个 View:
完成之后, 再次运行项目。
嗨!差不多像一个应用了!
下一步,我们将要添加用户界面部分。 在 Xcode 的 Utilities 边栏中, 切换到 Object Library。
拖动一个 “Round Rect Button” 到视图的中间,在屏幕上面。 双击你刚刚放置的按钮,设置 text 为 “-1”。
在 Utilities 边栏中, 选中 Attributes Inspector。 确保你刚刚放置的按钮处于选中状态。 设置 Background 属性为 “button_silver.png”。
按住 Option/Alt 键并且向右拖动 -1 按钮。 这样会创建一个它的拷贝。 双击这个新的按钮,并且设置它的文本为 “+1″。
拖动另一个 “Round Rect Button” 到屏幕的左边。 设置 Button Type 属性为 Custom 并且设置这个按钮的 Background 属性为 button_arrow_left.png。
恩,这看起来不太正确。 问题在于, 当你将图片设置为按钮的背景图片时, Xcode 会将图片拉伸到和按钮一样大。
如果你看一下文件本身, 你会看到它的尺寸为 19×33。 所以你要做的是,将按钮的尺寸设置到和图片一样大小。
选中这个按钮。 进入 Size Inspector, 然后分别设置 Width 和 Height 属性为 19 和 33。
看起来更进一步了! 现在你需要在另一边放一个向右的箭头按钮, 按住 Option/Alt 键,并且将刚才哪个按钮拖动到另一边。 修改这个新按钮的背景图片为 button_arrow.png。
还有最后一个按钮! 拖动另外一个 “Round Rect Button” 到黑板下面。
设置按钮的类型为 Custom 并且设置 Background 属性为 total_field.png。
我们需要再次将它的尺寸设置为和图片一样, 所以,选择 Size Inspector。 分别设置 Width 和 Height 为 134 和 51。 然后双击这个按钮,设置它的文本为 “Total”。
再次运行项目。
看起来很不错! 下面我们需要为 stroyboard 添加一些标签和预览区域。
再次选中 Object Library。 拖动一个 UILabel 到黑板那张图片上。 调整它的尺寸和黑板相同。
在 Attribute Inspector 中, 设置 Lines 属性为0 (这样会开启多行模式)。 修改 Text Color 的值为 white, 修改 Font 属性为 Marker Felt 17.0 (点击哪个 “T” 符号,选择 Custom,然后再选 Marker Felt)。 通常, 我觉得 Marker Felt 这个字体会是对眼睛的一个损伤,但这次看起来还可以。
拖动一个 UIImageView 到显示器中间,然后调整它的尺寸和显示器相等大小。
在 Attributes Inspector 中, 修改 Mode 属性为 Aspect Fit。
拖动另外一个 UILabel 到右下角的标志牌上面。 同样,调整它的尺寸到和灰色区域相同,并且设置 Alignment 属性为 Centered。
设置 IBOutlets 和 IBActions
现在我们需要在我们刚刚创建的用户界面和我们的代码之间建立连接。 这就是 IBOutlet 和 IBAction 的作用。 IB 指的是 Interface Builder ,用来在 Xcode 中创建用户界面。
- IBOutlet 基本上就是用户界面(例如,一个标签或一个按钮) 和我们代码中对这个元素的引用之间的一个连接。
- IBAction 是我们代码中的一个动作(或者叫方法,如果你喜欢),可以和我们设计的用户界面上的特定事件(例如,点击一个按钮) 绑定起来。
让我们来写一些代码通过 IBOutlet 和 IBAction 绑定 UI 控件吧。
关闭 Utilities 边栏并且打开 Assistant editor。根据你如何设置 Assistant editor 的显示方式, 你的屏幕上可能(或可能不是)看起来像下面的截图那样。 如果你想改变 Assistant editor 如何显示,你可以通过菜单栏中的 View – Assistant Editor 来做这件事。
让我们从按钮开始。 选中 “-1″ 按钮, 按住 Control 键并且拖动到编辑视图中的代码中。 这样会自动为这个按钮创建一个 IBOutlet。
对于这个对象,你需要做的所有事情就是给他命名一下, 我比较喜欢为所有的 outlet 都加上一个 “ib” 前缀, 这样能让我通过Xcode 的自动完成功能很容易的找到他们。 给这个对象命名为 “ibRemoveItemButton” 并点击 Connect。
现在,为 “+1″ 这个按钮做同样的事, 并给它命名为 “ibAddItemButton”。
下一步,为红色箭头的按钮设置 Outlet, 给他们命名为 “ibPreviousItemButton” 和 “ibNextItemButton”.
为 total 按钮设置 Outlet,并命名为 “ibTotalOrderButton”。
现在为 Label 和 Image View 设置 Outlet, 从左到右, 他们的名字为:
- ibChalkboardLabel
- ibCurrentItemImageView
- ibCurrentItemLabel
你的 IODViewController.h 现在看起来应该是这样:
@interface IODViewController : UIViewController @property (weak, nonatomic) IBOutlet UIButton *ibRemoveItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibAddItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibPreviousItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibNextItemButton; @property (weak, nonatomic) IBOutlet UILabel *ibChalkboardLabel; @property (weak, nonatomic) IBOutlet UIImageView *ibCurrentItemImageView; @property (weak, nonatomic) IBOutlet UILabel *ibCurrentItemLabel; @end |
最后, 我们将要为 UIButton 添加 IBAction。 这里有一些用于响应事件的方法 (Touch Up Inside, Touch Up Outside, Touch Cancel, 等等)。 对于按钮来说,我们用的最多的就是 Touch Up Inside。
再次选中 “-1″ 按钮。 按住 Control 然后再次拖动到 .h 文件中。
要记得修改 Connection type 为 Action。
这是一个 IBAction, 所以我给他一个前缀 “iba”。 如果你愿意跟随这个习惯, 把 outlet 命名为 “ibaRemoveItem”, 然后点击 Connect。
对 “+1″ 按钮进行同样的操作, 对这个 action 命名为 “ibaAddItem”。
还有,为红色的左三角按钮命名为 “ibaLoadPreviousItem”。
不要忘记红色右三角按钮。 他叫做 “ibaLoadNextItem”。
最后,为左下角的 “Total” 按钮添加一个 IBAction, 并给他命名为 “ibaCalculateTotal”。
设置 Web Service
在你开始编码之前, 是时候设置 web 服务了。 我不会对这个进行过多深入的讲解, 因为这个网站上已经有一些教程很好的讲解了他们 How To Write A Simple PHP/MySQL Web Service for an iOS App 和 How to Write an iOS App That Uses a Web Service
下面的代码展示了用 PHP 作为 web 服务的样子:
<?php function getStatusCodeMessage($status) { $codes = Array( 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' ); return (isset($codes[$status])) ? $codes[$status] : ''; } // Helper method to send a HTTP response code/message function sendResponse($status = 200, $body = '', $content_type = 'text/html') { $status_header = 'HTTP/1.1 ' . $status . ' ' . getStatusCodeMessage($status); header($status_header); header('Content-type: ' . $content_type); echo $body; } class InventoryAPI { function getInventory() { $inventory = array( array("Name"=>"Hamburger","Price"=>0.99,"Image"=>"food_hamburger.png"), array("Name"=>"Cheeseburger","Price"=>1.20,"Image"=>"food_cheeseburger.png"), array("Name"=>"Fries","Price"=>0.69,"Image"=>"food_fries.png"), array("Name"=>"Onion Rings","Price"=>0.69,"Image"=>"food_onion-rings.png"), array("Name"=>"Soda","Price"=>0.75,"Image"=>"food_soda.png"), array("Name"=>"Shake","Price"=>1.20,"Image"=>"food_milkshake.png") ); sendResponse(200, json_encode($inventory)); } } sleep(5); $api = new InventoryAPI; $api->getInventory(); ?> |
我的 web 服务是一个非常简单的 PHP 脚本, 返回一个 JSON 编码的数组。 这个数组里包含了 PHP 中的关联数组(在 Objective-C 中,这个叫做字典), 包含了名称,价格,和这个项目的图片。
唯一一个需要注意的事情,是在上面代码的倒数第三行的 sleep(5)。 我用它来模拟一个比较慢的 web 服务, 用来更好的展示 Block 如何帮助我们执行异步操作。
你可以将上面的代码拷贝到一个 .php 为扩展名的文件中,并且把他们放到某个主机上面或者干脆就用我提供的 http://adamburkepile.com/inventory/。
接下来去哪 ?
这个教程的一个实例项目可以在这里下载。
哇,这是一大堆和 Block 无关的东西! 但是现在我们完成了设置视图和 web 服务, 我们可以在下一环节进入有趣的部分了: 写代码!
继续第二部分的教程,这里我们将用 Block 让我们的的晚餐应用完善起来。
到现在,加入我们的论坛来一起讨论你的问题,意见和建议吧。
这篇文章来自 iOS 教程团队成员 Adam Burkepile, 一个全职软件咨询顾问和独立 iOS 开发者。 看看他最新的 app Pocket No Agenda , 或者在 Twitter 上面关注它。