开发iOS应用时,可能需要将UIControl对象(或UIControl子类对象,例如UIButton对象)加入某个UITableViewCell对象。以BNRItemCell为例,假设要添加如下功能:点击某个BNRItemCell中的缩略图时,显示相应BNRItem的全尺寸的图片。为了完成上述功能,本节将在UIImageView对象上添加一个透明的UIButton对象。此外,如果在iPad中点击UIButton对象,Homepwner需要使用UIPopoverController显示全尺寸图片。
打开BNRItemCell.m,添加一个空的动作方法,用于显示全尺寸图片:
- (IBAction)showImage:(id)sender
{
}
打开BNRItemCell.xib,拖曳一个UIButton对象至UIImageView对象,再删除UIButton对象的标题。同时选中UIImageView对象和UIButton对象,然后打开Align菜单,勾选Leading Edges、Trailing Edges、Top Edges和Bottom Edges四个选项。接下来在标题为Update Frames的下拉菜单中选择Items of New Constraints(匹配新约束),最后单击Add 4 Constraints添加约束(见图19-11)。
图19-11 UIButton对象的约束
最后需要将UIButton对象的动作方法设置为showImage:。在辅助编辑器中打开BNRItemCell.xib和BNRItemCell.m,然后按住Control,将UIButton对象拖曳至showImage:(见图19-12)。
图19-12 设置UIButton对象的动作方法
现在,点击按钮就可以将showImage:消息发送给BNRItemCell对象。下面需要实现showImage:——这里有一个问题:UIButton对象会将动作消息发送给相应的BNRItemCell对象,但是BNRItemCell对象不是控制器,无法访问全尺寸图片,甚至无法访问其当前显示的BNRItem对象。
虽然可以为BNRItemCell添加一个属性,指向其当前显示的BNRItem对象,但是BNRItemCell对象是视图对象,不应该直接访问模型对象,也不应该负责显示视图控制器(例如UIPopoverController)。
更好的解决方案是:通过BNRItemsViewController为BNRItemCell添加一个Block对象,当用户点击UIButton对象时,在Block对象中显示全尺寸图片。
为BNRItemCell添加Block对象
本书第17章曾简要介绍过Block对象,本章将深入学习Block对象。
打开BNRItemCell.h,添加一个Block属性,代码如下:
@interface BNRItemCell : UITableViewCell
@property (nonatomic, weak) IBOutlet UIImageView *thumbnailView;
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *serialNumberLabel;
@property (nonatomic, weak) IBOutlet UILabel *valueLabel;
@property (nonatomic, copy) void (^actionBlock)(void);
@end
读者可能会觉得语法有些奇怪,Block对象类似函数,有名称、参数和返回值,图19-13列出了Block对象的语法格式。
图19-13 Block对象的语法
请注意,actionBlock被声明为copy。系统对Block对象和其他对象的内存管理方式不同,Block对象是在栈中创建的,而其他对象是在堆中创建的。这意味着,即使应用针对新创建的Block对象保留了强引用类型的指针,一旦创建该对象的方法返回,那么与方法内部的其他局部变量相同,新创建的Block对象也会被立即释放。为了在声明Block对象的方法返回后仍然保留该对象,必须向其发送copy消息。拷贝某个Block对象时,应用会在堆中创建该对象的备份。这样,即使应用释放了当前方法的栈,堆中的Block对象也不会被释放。
在BNRItemCell.m的showImage:中调用Block对象,代码如下:
- (IBAction)showImage:(id)sender
{
// 调用Block对象之前要检查Block对象是否存在
if (self.actionBlock) {
self.actionBlock();
}
}
下面更新BNRItemsViewController.m中的tableView: cellForRowAtIndex- Path:,向控制台输出传入的NSIndexPath对象,测试是否可以正确执行Block对象:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
BNRItem *item = [[BNRItemStore sharedStore] allItems][indexPath.row];
// 获取BNRItemCell对象,返回的可能是现有的对象,也可能是新创建的对象
BNRItemCell *cell =
[tableView dequeueReusableCellWithIdentifier:@“BNRItemCell”
forIndexPath:indexPath];
// 根据BNRItem对象设置BNRItemCell对象
cell.nameLabel.text = item.itemName;
cell.serialNumberLabel.text = item.serialNumber;
cell.valueLabel.text =
[NSString stringWithFormat:@“$%i”, item.valueInDollars];
cell.thumbnailView.image = item.thumbnail;
cell.actionBlock = ^{
NSLog(@“Going to show image for %@”, item);
};
return cell;
}
构建并运行应用。点击某个缩略图(准确地说,是位于UIImageView对象上的透明UIButton对象),应该能在控制台看到相应的输出信息。
通过UIPopoverController显示图片
下面在BNRItemsViewController中修改BNRItemCell的actionBlock,根据UIButton对象所在的BNRItemCell对象,获取相应的BNRItem对象,然后在UIPopoverController中显示该BNRItem对象的图片。
要在UIPopoverController中显示图片,需要先准备好一个能够显示图片的UIViewController对象。使用Objective-C class文件模板创建一个名为BNRImage- ViewController的UIViewController子类,请注意不要勾选Also create XIB file。
BNRImageViewController只有一个视图,下面将通过代码创建视图。在BNRImageViewController.m中实现loadView,代码如下:
- (void)loadView
{
UIImageView *imageView = [[UIImageView alloc] init];
imageView.contentMode = UIViewContentModeScaleAspectFit;
self.view = imageView;
}
这里不需要为UIImageView对象添加任何约束。BNRImageViewController显示在UIPopoverController中,UIPopoverController会自动将UIImageView对象(BNRImageViewController的view)的大小调整为与自身一致。
下面在BNRImageViewController.h中添加一个属性,用来保存需要显示的UIImage对象:
@interface BNRImageViewController : UIViewController
@property (nonatomic, strong) UIImage *image;
@end
创建BNRImageViewController对象后,要将相应的UIImage对象赋给image属性。在BNRImageViewController.m中实现viewWillAppear:,将image显示到UIImageView中:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 必须将view转换为UIImageView对象,以便向其发送setImage:消息
UIImageView *imageView = (UIImageView *)self.view;
imageView.image = self.image;
}
现在可以完成actionBlock了。在BNRItemsViewController.m中,首先添加一个属性,用于保存UIPopoverController,然后将BNRItemsViewController声明为遵守UIPopoverControllerDelegate协议,代码如下:
@interface BNRItemsViewController ()<UIPopoverControllerDelegate>
@property (nonatomic, strong) UIPopoverController *imagePopover;
@end
接下来在BNRItemsViewController.m顶部导入需要的头文件:
#import “BNRImageStore.h”
#import “BNRImageViewController.h”
最后在actionBlock中显示UIPopoverController:
cell.actionBlock = ^{
NSLog(@“Going to show the image for %@”, item);
if ([UIDevice currentDevice] userInterfaceIdiom == UIUserInterfaceIdiomPad) {
NSString *itemKey = item.itemKey;
// 如果BNRItem对象没有图片,就直接返回
UIImage *img = [[BNRImageStore sharedStore] imageForKey:imageKey];
if (!img)
return;
}
// 根据UITableView对象的坐标系获取UIImageView对象的位置和大小
// 注意:这里也许会出现警告信息,下面很快就会讨论到
CGRect rect = [self.viewconvertRect:cell.thumbnailView.bounds
fromView:cell.thumbnailView];
// 创建BNRImageViewController对象并为image属性赋值
BNRImageViewController *ivc = [[BNRImageViewController alloc] init];
ivc.image = img;
// 根据UIImageView对象的位置和大小
// 显示一个大小为600x600点的UIPopoverController对象
self.imagePopover = [[UIPopoverController alloc]
initWithContentViewController:ivc];
self.imagePopover.delegate = self;
self.imagePopover.PopoverContentSize = CGSizeMake(600, 600)];
[self.imagePopover presentPopoverFromRect:rect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
};
最后,在BNRItemsViewController.m中实现popoverControllerDidDismissPopover:,当用户关闭UIPopoverController时,将UIPopoverController设置为nil:
-(void)popoverControllerDidDismissPopover:
(UIPopoverController *)popoverController
{
self.imagePopover = nil;
}
在iPad模拟器中构建并运行应用。点击某个BNRItemCell对象中的缩略图,Homepwner应该会弹出一个UIPopoverController对象,并显示相应BNRItem对象的全尺寸图片。点击其他区域可以关闭UIPopoverController对象。