Ray的提示:这是我们新书iOS 6 Feast的第四篇教程!这篇教程出自我们的新书iOS 6 By Tutorials。写这篇教程的作者是Brandon Trebitowski – 他是我们教程团队的新成员并且也是我的好朋友。让我们开始吧!
这篇教程发自我们的教程团队成员 Brandon Trebitowski, 他是一名软件工程师,他会定期在 brandontreb.com发表文章。
当苹果在2010年首发iPad的时候,你可能对绑定在iPad上的图片应用程序印象很深刻。通过它多元化的布局方案,照片展示可以表现的如此别出心裁。你可以在一个漂亮网格视图下查看你的照片:
你的照片专辑也可以展示成一个照片堆:
你甚至可以通过和一个很酷的手势来切换这两种视图。“哇哦,我也想在我的应用里用到这个!”,你可能会这么想。
当然,你自己也可以完成这个网格视图以及其他一些可变化的布局,但这需要相当的技巧性!这需要许多许多代码来完成并且还不一定能完全到达效果。那么是不是有更简便的方法呢?
好消息 – 现在是iOS 6 了!Apple 引进了一个新的类叫做UICollectionView,它能使你的自定义照片布局构造变的不可思议的简单。
因为UICollectionView的自定义扩展性,我们不仅能做出照片堆和照片网格视图效果,还能做出圈子布局,覆盖流布局, 脉冲新闻格式布局 – 以及你能想到的任何布局!
所以至少你需要学习一个足够强大的新方式来展现你定制的数据给用户看。这个新方式最好和和UITableView一样重要和有用。现在好消息是,如果你对UITableview足够熟悉的话,那么你对UICollectionView的学习也会易如反掌 – 它的模式和table view的 data source和delegate很类似。
在这篇教程里,你将通过创建你自己的网格试图照片应用来亲手实践UICollectionView。 学完后,你将知道UICollectionView基本运行原理并且可以开始准备在你的应用里使用这个惊艳的技术了!
请看, UICollectionViewController 组件包括了以下的重要组件:
让我来一个个的解释他们:
除了上述的一些视觉组件外,UICollectionView也有另外一些非视觉组件可以来帮助展示内容:
在我的这份教程里后面将重点介绍这些元素对象。但现在,我们还是亲手来做一个项目吧!
在这章教程的余下部分,你将会创建一个名叫FlickrSearch的很酷的应用。它的功能有,帮助你在Flickr网站上下载一些很时尚的照片,然后展现在我们的一个很漂亮的木板公告栏主题的网格视图上:
在开始前,请先确定你已经 下载了我们将用到的素材!
好了,准备好了吗?打开Xcode并且 FileNewProject… 然后选择 iOSApplicationSingle View Application 模板。
这个模板只是提供你一个简单UIViewController和storyboard让你开始,没有其他的了。这样的几乎“从零开始”会比较好。
点击Next来填完这个应用的其他一些信息。把项目名设为 FlickrSearch,设备类型选为 iPad, 并且确认Use Storyboards 和Use Automatic Reference Counting 框里的勾被选上了。点击 Next选择项目存储的地方,然后点击Create。
编译运行看看,呵呵,你只是得到了一个简单的空白view。
下一步你应该导入刚下载的资料,把解压后的图片资料拖进你Xcode里的项目。并且确认 Copy items into destination group’s folder (如果需要的话)框里的勾是被选上的。点击Finish。
现在你将开始制作一个基础但又新颖的设计来使你的应用看上去有个性。创建完这个后,我们再把UICollectionView加进去。
在你打开storyboard之前,请先在interface里面声明一些IBOutlets和IBActions。打开ViewController.m然后在顶部的@interface下面更新如下代码:
@interface ViewController () @property(nonatomic, weak) IBOutlet UIToolbar *toolbar; @property(nonatomic, weak) IBOutlet UIBarButtonItem *shareButton; @property(nonatomic, weak) IBOutlet UITextField *textField; - (IBAction)shareButtonTapped:(id)sender; @end |
在文件的最底部请加上 shareButtonTapped: 这个方法 (我们待会再来写它):
-(IBAction)shareButtonTapped:(id)sender { // TODO } |
为了在implementation文件来声明这些outlets和actions,因为它们只需对我们的ViewController类可见。现在是时候把他们连接起来了。
打开MainStoryBoard.storyboard。从Object库里面拖一个Toolbar对象到main view里面去,并且把它左上角button的text改为Share。改text你可以双击改也可以在Atrributes Inspector的title属性里面改。
下一步,拖拽左边bar里的Share按钮到ViewController,把它和shareButtonTapped:方法连接起来。
下一步,添加一个搜索label和一个搜索box。在你的main view拖进一个imageView并将它的image属性设为search_text.png。就目前来看,这个image看上去很糟糕,但你可以修复它。把它的mode属性设为中心并且将它移动到toolbar下面。或者,你也可以使用EditorSize 里的Fit Content 菜单选项来使image view自动安排它的内容到一个合适的尺寸。
提示: 如果你想知道为什么使用一个image view而不是一个label,这将是一个好问题。在现在这种特殊情况下,你想要的是text有一个特殊明确的呈现。所以你用了一个适合你应用主题的image。但这种做法有一个明显的缺点,那就是它使得语言本地化变难了,不过好处是显然易见的,如果你的应用只做支持一种语言,那么用这种方法是最简单的。
对于search box,把一个text field控件拖到你的view里面,并使它与你search label的右边对齐。将它border style设为none,你待会可以通过代码来自定义它的样式。
在你添加完textfield之后,通过连接viewcontroller里面的textfield与左边bar里的textfield,把textfield的“delegate”设为viewController。或者,在你的代码里设置也可以。设置textfield的delegate是为了你在其delegate方法里写额外的代码来取消掉textfield弹出的keyboard。
最后,在search box下方添加一条线来分割结果显示区。做这个,你可以通过在你的search box下方拖进另外一个image view来实现,把image view的image属性设为divider_bar.png, 把它的位置设到屏幕中间(或者将它的尺寸设为fit the content)。如下所示:
最后一步是把这些IBOutlets连接起来。点击view controller的左边bar里面的对象与Connections Inspector里对象建立起关系。从你建立的每个IBOulets对象(shareButton,textField, 和toolbar)拖到他们各自对应的界面元素。
现在有趣的来了, 你要自定义view来使得它不同于storyboary里面显示的样子。打开ViewController.m把如下代码加入到viewDidLoad:下面去。
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bg_cork.png"]]; UIImage *navBarImage = [[UIImage imageNamed:@"navbar.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(27, 27, 27, 27)]; [self.toolbar setBackgroundImage:navBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault]; UIImage *shareButtonImage = [[UIImage imageNamed:@"button.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(8, 8, 8, 8)]; [self.shareButton setBackgroundImage:shareButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; UIImage *textFieldImage = [[UIImage imageNamed:@"search_field.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)]; [self.textField setBackground:textFieldImage];  |
这步把整个view的背景设置成了与软木板主题一致的背景色(使用方便的UIColor:colorWithPattenImage方法),别且也设置了toolbar,share button,和text field的背景图片。
编译并且运行你的应用。现在它的初始UI应该是下图所示的样子:
不错 – 这个初始界面还不错哦!它看上去像一个你想在这上面粘贴各种样式照片的公告板。在这篇教程的剩下部分,你将使用UICollectionView来把这种设计实现!
Flickr是一个奇妙的图片分享服务,并且它提供给开发者一个供公众访问而且非常的简单API。它的API支持功能search 照片,添加照片,评论照片,等等。
如果使用它的API的话,你要一个API key。如果你真的打算做一个项目的话,我推荐你注册一个:
http://www.flickr.com/services/api/keys/apply/。
不过对于测试项目来说,Flickr提供了给大家一个不用注册但会定期更新的示例key。通过简单的http://www.flickr.com/services/api/explore/?method=flickr.photos.search 在后边跟上“&api_key=”,把key粘贴在“=”之后。现在你可以把它粘贴在文本编辑器中,待会还有用。
比如说,现在的URL是:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783 efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238 d5e2f
这里API key是:6593783efea8e7f6dfc6b70bc03d2afb
提示: 如果你使用示例API key,请记住它会定期改变的。可能在你现在完成这个项目后几天,你的API key就变了。所以为了这个理由,请你自己申请一个API吧,如果几天后你还想要用到这个工程来参考。
既然这篇教程是关于UICollectionView而不是Flickr API的,所以我已经写好一个抽象的Flickr调用类了。你可以下载 它们。
把这些文件拖进你的工程,请确认Copy items into destination group’s folder (如果需要的话)是被选中了的,然后点击完成:
现在有两个类你需要导入:
请随意检查我的代码 – 它简单得可能会令你想到把它用到你自己的项目中去!
如果你准备步入下一部分了 – 那我们现在开始做一点融合Flickr的准备工作。
这里你需要考虑的是,当你每次提交一个查询时,它将在collection view中展示出一个包含查询结果新的“section” (而不是简单的覆盖之前查询的)。 换句话说,比如你查询了“忍者”和“海盗”,那么在tableview中应该各有一个他们的section来展示他们。
为了达到这种效果,你需要创建一个数据结构来使得你能在每个单独的section里面保存数据。如果你认为NSMutableDictionary将会是一个不错的选择,那么你答对了。dictionary的keys应该是搜索条件,values将会是FlickrPhoto对象的array。
让我们来通过创建array和dictionary来存储搜索条件和结果,通过创建Flickr对象来执行查询。打开ViewController.m 并导入以下类:
#import "Flickr.h" #import "FlickrPhoto.h" |
接下来,在@interface里面加入以下属性声明:
@property(nonatomic, strong) NSMutableDictionary *searchResults; @property(nonatomic, strong) NSMutableArray *searches; @property(nonatomic, strong) Flickr *flickr; |
在viewDidLoad里面初始化这些属性:
self.searches = [@[] mutableCopy]; self.searchResults = [@{} mutableCopy]; self.flickr = [[Flickr alloc] init]; |
searches是一个用来跟踪在app里所做的所有搜索的array, searchResults 将关联查询条件对应的结果。
接下来,你将会学到如何通过用户的输入来填充这些属性。
在你能搜索Flickr之前,你需要首先输入API key。打开Flickr.m文件然后把kFlickrAPIKey 用你的key来替换掉。如下所示:
#define kFlickrAPIKey @"ca67930cac5beb26a884237fd9772402" |
现在使用Flickr来搜索已经准备好了!切换到ViewController.m文件然后把以下代码加到文件的底部(在@end之前哦):
#pragma mark - UITextFieldDelegate methods - (BOOL) textFieldShouldReturn:(UITextField *)textField { // 1 [self.flickr searchFlickrForTerm:textField.text completionBlock:^(NSString *searchTerm, NSArray *results, NSError *error) { if(results && [results count] > 0) { // 2 if(![self.searches containsObject:searchTerm]) { NSLog(@"Found %d photos matching %@", [results count],searchTerm); [self.searches insertObject:searchTerm atIndex:0]; self.searchResults[searchTerm] = results; } // 3 dispatch_async(dispatch_get_main_queue(), ^{ // Placeholder: reload collectionview data }); } else { // 1 NSLog(@"Error searching Flickr: %@", error.localizedDescription); } }]; [textField resignFirstResponder]; return YES; } |
当用户点击键盘上的Enter键时, 这个方法会被呼叫(因为你之前已经把View controller设为text field的delegate了)。这里是这段代码的解释:
现在运行你的app,在text box里面输入查询字符并执行,你应该在控制台看到一个关于searches 结果的信息,如下:
2012-07-10 21:44:16.505 Flickr Search[11950:14f07] Found 18 photos matching 1337 h4x 2012-07-10 21:44:32.069 Flickr Search[11950:14f0b] Found 20 photos matching cat pix |
注意为了保证加载时间短,Flickr将搜索结果限制在20个以内。
现在你已经有了一个照片来展示,我们也终于到了写UICollectionView来将他们展示到屏幕上去!
你可能已经知道,当你在使用UITableView的时候,你必须要设置一个data source 和一个delegate来分别提供data用于显示和提供方法用于操控。(比如选中row后)
类似地,当你使用一个UICollectionView的时候你也必须设置一个data source和一个delegate。它们的详细说明如下:
但是当添加新的UICollectionView的时候,你必须执行第三个协议 – 这个协议是用来定制layout manager的。在这片教程里你将会用到预设的UICollectionViewFlowLayout 的layout manager,所以你必须执行UICollectionViewDelegateFlowlayout协议。它将帮你调整布局行为,控制cell之间的间距,滚动的方向等等。
在这里,你将要执行的协议方法有UICollectionViewDataSource, UICollectionViewDelagate和UICollectionViewDelegateFlowLayout, 这也将是你的colletion view所需要的所有方法。
让我们先在ViewController.m 文件的@interface下面加入UICollectionViewDelegateFlowlayout和UICollectionViewDataSource协议。如下:
@interface ViewController () |
提示: 你可能想知道为了我们加了UICollectionViewDelegateFlowLayout 而没有加UICollectionViewDelegate 呢?这是因为UICollectionViewDelegateFlowLayout 实际上是UICollectionViewDelegate 的一个子协议,它继承了UICollectionViewDelegate ,所以这里没有必要两个都加了。
现在让我们来执行这些协议方法吧!
让我们先从data source开始,在ViewController.m:下方加入如下代码:
#pragma mark - UICollectionView Datasource // 1 - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section { NSString *searchTerm = self.searches[section]; return [self.searchResults[searchTerm] count]; } // 2 - (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView { return [self.searches count]; } // 3 - (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell " forIndexPath:indexPath]; cell.backgroundColor = [UIColor whiteColor]; return cell; } // 4 /*- (UICollectionReusableView *)collectionView: (UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { return [[UICollectionReusableView alloc] init]; }*/ |
好的,加完了。那么现在这些方法究竟做了什么呢?相信我,读一下:
做完UICollectionViewDataSource后,我们再来看看UICollectionViewDelegate。在ViewController.m:最后加入如下代码:
#pragma mark - UICollectionViewDelegate - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // TODO: Select Item } - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { // TODO: Deselect item } |
目前,我们先把这些方法空白好了。就像他们名字所示,当你选择一个cell或者放弃选择一个cell的时候,这些方法会启动。提示:collectionView:didDeselectItemAtIndexPath:方法只有在UICollectionView允许多选情况下才会被调用到 – 你稍微会看到如何使用的。
就像我之前提到的,每个UICollectionView有一个相关的layout。对于这个项目,我们将使用预设的UICollectionViewFlowLayout, 因为它会给你一个像网格视图那样简单又漂亮的布局。
还是在 ViewController.m下面添加如下代码::
#pragma mark – UICollectionViewDelegateFlowLayout // 1 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { NSString *searchTerm = self.searches[indexPath.section]; FlickrPhoto *photo = self.searchResults[searchTerm][indexPath.row]; // 2 CGSize retval = photo.thumbnail.size.width > 0 ? photo.thumbnail.size : CGSizeMake(100, 100); retval.height += 35; retval.width += 35; return retval; } // 3 - (UIEdgeInsets)collectionView: (UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { return UIEdgeInsetsMake(50, 20, 50, 20); } |
你可以另外再写一些协议方法来执行,但是对于这个项目,这些就够了。
基于以上几步,我们现在已经做好加UICollectionView和一些相关的subview的准备了。
使用UICollectionView最大的好处是它和table view很像, Apple已经把定制的不可意思的简单,你可以直接在Storyboard editor里面来界面操作设置它。你可以把UICollectionView在你的View controller里面拖进拖出,然后在Storyboard editor里面直接设置UICollectionViewCell。让我们看看具体如何操作。
在你的Storyboard添加collection view之前,我们先设置一个它要引用的IBOutlet。在ViewController.m下,添加如下代码:
@property(nonatomic, weak) IBOutlet UICollectionView *collectionView; |
现在打开MainStoryboard.storyboard然后拖进一个collection view对象(提示:是collection view而不是collection view controller哦!)。把它放到line 图像下并且让它撑满下面的所有空白:
为了让大家看的清楚些,我把它的背景色设为蓝色,但是你不要也设置为蓝色哦,它的背景色应该是transparent/clear。
接下来,你需要设置collection view的delegate和datasource了。按住control把collection view拖到view controller的inspector里,然后依次选择dataSource和delegate。
最后,在view controller的 inspector里面切换到Connections Inspector( 你可以通过选择右上角sidebar然后点击 ViewUtilitiesShow Connections Inspector)。从你的collectionView 属性把连接线拖进你storyboard里的collection view对象。
现在连接都已经做好了,你一定很想看看效果吧。呵呵,别急,我们还有两步需要操作。第一部是在通知UICollectionView 如何来创建它自己的cell。
在 ViewController.m的viewDidLoad 的最后面加入:
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"FlickrCell"]; |
现在,每当collection view需要创建一个cell的时候,它将使用默认的UICollectionViewcell类。稍后你将写到你自定义的cell,但现在我们先不管这些。
最后一步是重新加载collection view的视图当新的数据加载后。在 ViewController.m回到textFieldShouldReturn:。然后用以下代码把注释“// Placeholder: reload collectionview data”替代掉:
[self.collectionView reloadData]; |
编译并运行,然后开始搜索。如果你搜索的,你应该可以看到这些view以白色小盒子弹出了,并且小盒子的块之间有一大块空白的,那就是header view。
恭喜你! – 你的collection view在显示结果的每行row之间有空行了!
现在的应用我们通过查询还不能展示实际的images,现在我们是时候来执行一些用images来填满自定义UICollectionViewCell的代码了。
默认的,UICollectionViewCells不允许除了更改背景颜色以外的自定义操作。所以你只能创建你自己的UICollectionViewCell子类。
在你开始前,请确认你已经把ViewController.m的viewDidLoad里的这一行注释掉了:
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"FlickrCell"]; |
这只是我们之前的缓兵之计,现在我们不需要了,否则程序会出错的。
到 FileNewFile…, 选择 iOSCocoa TouchObjective-C 类模板,然后点击 Next. 把该类命名为 FlickrPhotoCell,将之设置为UICollectionViewCell的子类然后点击 Next。最后,选择一个存它的地方后点击 Create。
我们的FlickrPhotoCell 应该只有一个subview, 也就是我们用来展示从Flickr上加载到的图片的UIImageView。在你创建你的UI前,我们设置它的类为FlickrPhotoCell:
#import <QuartzCore/QuartzCore.h> @class FlickrPhoto; @interface FlickrPhotoCell : UICollectionViewCell @property (nonatomic, strong) IBOutlet UIImageView *imageView; @property (nonatomic, strong) FlickrPhoto *photo; @end |
你创建的UIImageView outlet应该是public的,应该其他的一些类可能需要在你的image异步加载后来修改它。你也需要对你展示的图片来添加一个引用,因为将来你会需要它的信息。现在让我们创建这个view。
当你把UICollectionView添加到你的main view时,Interface Builder会自动为你创建一个UICollectionViewCell。在Interface Builder里打开MainStoryboard.xib。扩展“collection view”的小三角来启用cell。有两个基本步骤需要操作,一个是设置cell的类,另一个是设置它的identifier。
点击“Collection View Cell”的小三角然后打开Identity Inspector。在类名字里面填写FlickrPhotoCell。
现在,打开它的Attributes Inspector然后在Identifier 框里填写FlickrCell 。这个identifier 将在之后的cellForItemAtIndexPath 方法里用到。
接下来,把cell缩放到大概300 × 300 pixels。这里不必太抠尺寸细节,因为我们的view会动态缩放在我们的delegate方法里。
把image view拖到main view里。将它的尺寸缩放到正好填满cell view。请确认你放的地点正好能使所有的蓝色guides紧靠四边。这是唯一一个你必须添加的user constraint来使这个layout运行正常。选中image view,然后点击user constraints图标,再选择“Bottom space To Superview”。
现在,打开Attributes Inspector然后把它的模式改为Aspect Fit来使Flickr 照片尺寸正合适与image view的框架。
在左边的sidebar里选中Flickr Photo Cell然后打开Connections Inspector。把imageView 的outlet与你的UIImageView相连。
最后我们自定义view的最后一步是在顶部添加pushpin。把另一个image view拖到你的view上面,这次把它放到你cell的顶部的中心位置。同样地,在Attributes Inspector里面把它的mode选为center另外image选为pushpin.png。最终样板如下所示:
做完这些后,你肯定也想把这些view用内容填满了吧。
现在开始通知UICollectionView来使用你的FlickrPhotoCell类而不是默认的那个了。打开ViewController.m然后修改如下代码:
#import "FlickrPhotoCell.h" |
Now, replace collectionView:cellForItemAtIndexPath: with this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath { FlickrPhotoCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath]; NSString *searchTerm = self.searches[indexPath.section]; cell.photo = self.searchResults[searchTerm] [indexPath.row]; return cell; } |
这段代码第一件做的事情就是用FlickrPhotoCell 来替代原来的UICollectionViewCell。它会根据你在Storyboard里面设置的FlickrCell的这个identifier来决定使用哪种cell样式。接下来,它就根据你引用的照片来设置cell的photo。
编译并运行程序,做一个搜索,然后观察结果。
好的,现在UI是越来越接近你理想的了。至少它现在在用你定制的UICollectionViewCell了,但是为什么它还是不显示照片了呢?
因为当你在设置你cell照片属性的时候,你并没有更细UIImageView的image。修复它也很简单,让我们来重写photo属性的setter方法就行了。首先,把如下代码写到FlickrPhotoCell.m的顶部:
#import "FlickrPhoto.h" |
Then add the following to the end of the file (but before @end):
-(void) setPhoto:(FlickrPhoto *)photo { if(_photo != photo) { _photo = photo; } self.imageView.image = _photo.thumbnail; } |
再次运行程序然后做一个搜索。这下你可以看到每个cell的照片了吧!
是的!成功了!可以看到现在每个照片都非常完美的嵌在它的cell里。这主要归功与你在sizeForItemAtIndexPath里面设置了cell的尺寸是照片尺寸再加35pixels,也归功于你修改的Auto Layout设置。
提示: 如果你的view看上去和我们的不同或者显示的很奇怪,那么这很可能你的Auto Layout设置的不对。如果你卡住了,那么尝试比较一下我这里是怎么做的。
sizeForItemAtIndexPath方法里的代码为我们保证了每个cell比它里面的图片宽35pixels和高35pixels,并且Auto Layout规则确保了在每个新的cell frame里面image view的尺寸缩放问题和保证image view始终在中心位置。
那么到现在,你已经完成了一个UICollectionView的实例(很酷吧)- 以后请多复习复习!
我们还有更多要学的呢!在这部教程的第二部分,你将学到:
如果你还想学的更多,那就请参阅我们的新书iOS 6 by Tutorials,里面有一个更高级的教程会让你了解在你的UICollectionViews下如何使用自定义的layouts。
与此同时,如果你有任何的问题或者意见,请加入到下方的论坛讨论吧!
这片博客教程由我们的iOS教程团队队员 Brandon Trebitowski发布,这位程序员会定期在brandontreb.com发表博客。
我目前是一名自由IOS开发者,很高兴由我来为大家翻译这篇教程,有什么建议可以发信([email protected])至我。