iOS8下的UIActionSheet

前段时间做了一个iPad应用,里面用到了图片上传。因此,需要用到UIImagePickerController打开本地图库和开启相机拍照。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (IBAction)choosePhoto:(id)sender
{
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"选择照片"
delegate:self
cancelButtonTitle:@"取消"
destructiveButtonTitle:@"照片库"
otherButtonTitles:@"拍一张", nil];
[sheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == actionSheet.cancelButtonIndex) {
return;
}
UIImagePickerController *picker = [[UIImagePickerController alloc]init];
if (buttonIndex == 1) {
[picker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
}else{
[picker setSourceType:UIImagePickerControllerSourceTypeCamera];
}
[self presentViewController:picker animated:YES completion:nil];
}

在iOS 7之前的模拟器以及iOS 7模拟器上测试都没问题。但是,iOS 8上报了如下警告:

Warning: Attempt to present <UIImagePickerController: 0x7f96c28f7800> on <ViewController: 0x7f96c302c3d0> which is already presenting <UIAlertController: 0x7f96c337be90>

根据问题可以看出ViewController在present UIImagePickerController之前present了UIAlertController,但是,我并没有是用UIAlertView。但是这里出现了UIAlertController这个iOS 8推出的新的类,问题应该就出现在这里。然后,我在UIActionSheet头文件找到了这样一句话:

UIActionSheet is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleActionSheet instead

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (IBAction)choosePhoto:(id)sender
{
[self presentViewController:self.actionSheet
animated:YES
completion:nil];
}
- (UIAlertController *)actionSheet
{
if (_actionSheet == nil) {
_actionSheet = [UIAlertController alertControllerWithTitle:@"选择照片"
message:@"请通过以下方式来选择照片"
preferredStyle:UIAlertControllerStyleActionSheet];
// 在action sheet中,UIAlertActionStyleCancel不起作用
UIAlertAction *act1 = [UIAlertAction actionWithTitle:@"相机" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
}];
UIAlertAction *act2 = [UIAlertAction actionWithTitle:@"图库" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
}];
UIAlertAction *act3 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
}];
[_actionSheet addAction:act1];
[_actionSheet addAction:act2];
[_actionSheet addAction:act3];
}
return _actionSheet;
}

结果,运行时程序直接崩掉了。错误信息:

Terminating app due to uncaught exception ‘NSGenericException’, reason: ‘Your application has presented a UIAlertController (<UIAlertController: 0x7f8e5841e790>) of style UIAlertControllerStyleActionSheet. The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller’s popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.’

错误信息已经详细的描述了问题所在,以及解决方法。也就是当UIAlertController以UIAlertControllerStyleActionSheet类型展示的时候,必须借助于UIPopoverPresentationController这个类。

注意:UIPopoverPresentationController是UIPresentationController的一个子类,此类日后可能会取代UIPopoverController,并且可以在iPhone应用中实现以前只有iPad才有的UIPopoverController功能。

于是,将上面的代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (IBAction)choosePhoto:(id)sender
{
UIPopoverPresentationController *pop = self.actionSheet.popoverPresentationController;
pop.delegate = self;
pop.sourceView = self.view;
// 显示在中心位置
pop.sourceRect = CGRectMake((CGRectGetWidth(pop.sourceView.bounds)-2)*0.5f, (CGRectGetHeight(pop.sourceView.bounds)-2)*0.5f, 2, 2);
[self presentViewController:self.actionSheet animated:YES completion:nil];
}
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
NSLog(@"%s", __PRETTY_FUNCTION__);
}
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
NSLog(@"%s", __PRETTY_FUNCTION__);
return YES;
}
- (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
NSLog(@"%s", __PRETTY_FUNCTION__);
}
- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view
{
NSLog(@"%s", __PRETTY_FUNCTION__);
}

此时,运行结果如下:

image

但是,当我旋转屏幕时,问题又出现了。

image

从图中可以看出,actionSheet的位置不在使我们想要的。解决办法如下:

1
2
3
4
5
6
7
8
- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view
{
NSLog(@"%s", __PRETTY_FUNCTION__);
// 显示在中心位置
*rect = CGRectMake((CGRectGetWidth((*view).bounds)-2)*0.5f, (CGRectGetHeight((*view).bounds)-2)*0.5f, 2, 2);
}

运行结果如下:

image

至此,iOS 8下地UIActionSheet处理就完成了。在iOS 8下面使用这个还是有点小复杂,那么还有没有其他的方法呢?答案是肯定的,可以在本文开始的代码做如下处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (IBAction)choosePhoto:(id)sender
{
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"选择照片"
delegate:self
cancelButtonTitle:@"取消"
destructiveButtonTitle:@"照片库"
otherButtonTitles:@"拍一张", nil];
[sheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == actionSheet.cancelButtonIndex) {
return;
}
UIImagePickerController *picker = [[UIImagePickerController alloc]init];
if (buttonIndex == 1) {
[picker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
}else{
[picker setSourceType:UIImagePickerControllerSourceTypeCamera];
}
[self presentViewController:picker animated:YES completion:nil];
}

这样也是可以解决前面的警告错误的。因为,现在是在 didDismissWithButtonIndex: 方法里面处理,也就是此时UIActionSheet已经从界面消失了之后再 present UIImagePickerController。也就避免了同时 present 两个控制器。如果你考虑 iOS 8 以下版本的支持,那么推荐使用此方法。


原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0