首页 » iOS编程(第4版) » iOS编程(第4版)全文在线阅读

《iOS编程(第4版)》19.3 由UITableViewCell对象转发动作消息

关灯直达底部

开发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对象。