如何创建将在 iPad 上使用弹出窗口并推送到 iPhone 上的导航堆栈的 segue?
How can I create a segue that will use a popover on iPad, and push onto the navigation stack on the iPhone?
在我的应用程序中,某些视图控制器在 iPad(或更具体地说,常规水平尺寸 class)上将它们呈现为弹出窗口是有意义的,但在 iPhone(或紧凑的水平尺寸 class)将它们推到导航堆栈上是有意义的。有没有一种优雅的方式来支持这个?默认情况下,如果我使用 "Present as Popover" segue,它将在 iPhone 上模态显示,这不是我想要的。
我找到了一种方法来获得我想要的行为,但它很丑陋而且似乎容易出错。我根据当前大小 class 在两个不同的 segue 之间进行选择。为了支持 iOS 9 多任务处理,我实现了 [UIViewController willTransitionToTraitCollection:withTransitionCoordinator]
并在弹出窗口和弹出窗口之间手动移动视图控制器导航控制器(这部分似乎特别容易出错)。
似乎应该有一些简单的方法来实现自定义 segue 来处理这个问题,或者实现某种自定义自适应表示控制器,但我一直无法理解它。有人成功过吗?
在我看来这是最简单的方法,
第 1 步:创建从一个控制器到另一个控制器的两个 segue。
第 2 步: 将一个 segue 的 segue 属性 设置为另一个
的 push 和 popover
第 3 步: 现在根据您的要求调用执行 segue,i.e.iPad 或 iPhone
示例代码注意:将 bool 条件更改为 false 以检查 didSelectRowAtIndexPath
.
中的另一个条件
这是我最终构建的内容。我对它不是很满意,这就是为什么我直到现在才发布它。它不支持使用相同 class 查看控制器的两个 segue,并且它需要您自己跟踪弹出窗口的源矩形和源视图。但也许这对其他人来说是一个很好的起点。
PushPopoverSegue.swift
import UIKit
class PushPopoverSegue: UIStoryboardSegue {
var sourceBarButtonItem: UIBarButtonItem!
var permittedArrowDirections: UIPopoverArrowDirection = .Any
override func perform() {
assert( self.sourceViewController.navigationController != nil )
assert( self.sourceBarButtonItem != nil )
if self.sourceViewController.traitCollection.horizontalSizeClass == .Compact {
self.sourceViewController.navigationController!.pushViewController(self.destinationViewController, animated: true)
}
else {
let navigationController = UINavigationController(rootViewController: self.destinationViewController)
let popover = UIPopoverController(contentViewController: navigationController)
popover.presentPopoverFromBarButtonItem(self.sourceBarButtonItem, permittedArrowDirections: self.permittedArrowDirections, animated: true)
}
}
}
UIViewController+PushPopoverTransition.h
#import <UIKit/UIKit.h>
@interface UIViewController (PushPopoverTransition)
- (void) transitionPushPopoversToHorizontalSizeClass: (UIUserInterfaceSizeClass) sizeClass withMapping: (NSDictionary*) mapping;
@end
UIViewController+PushPopoverTransition.m
#import "UIViewController+PushPopoverTransition.h"
@implementation UIViewController (PushPopoverTransition)
- (void) transitionPushPopoversToHorizontalSizeClass: (UIUserInterfaceSizeClass) sizeClass withMapping: (NSDictionary*) mapping
{
if ( sizeClass == UIUserInterfaceSizeClassCompact )
{
if ( self.presentedViewController == nil )
return;
NSParameterAssert( [self.presentedViewController isKindOfClass:[UINavigationController class]] );
UINavigationController* navigationController = (UINavigationController*) self.presentedViewController;
NSArray* viewControllers = navigationController.viewControllers;
UIViewController* topOfStack = viewControllers[0];
if ( [mapping.allKeys containsObject:NSStringFromClass( [topOfStack class] ) ] )
{
[self.presentedViewController dismissViewControllerAnimated:NO completion:^{
for ( UIViewController* viewController in viewControllers )
[self.navigationController pushViewController:viewController animated:NO];
}];
}
}
else if ( sizeClass == UIUserInterfaceSizeClassRegular )
{
NSUInteger indexOfSelf = [self.navigationController.viewControllers indexOfObject:self];
if ( indexOfSelf < self.navigationController.viewControllers.count - 1 )
{
UIViewController* topOfStack = self.navigationController.viewControllers[indexOfSelf + 1];
if ( [mapping.allKeys containsObject:NSStringFromClass( [topOfStack class] )] )
{
NSArray* poppedControllers = [self.navigationController popToViewController:self animated:NO];
UINavigationController* navigationController = [[UINavigationController alloc] init];
navigationController.modalPresentationStyle = UIModalPresentationPopover;
navigationController.viewControllers = poppedControllers;
id popoverSource = mapping[NSStringFromClass( [topOfStack class] )];
if ( [popoverSource isKindOfClass:[UIBarButtonItem class]] )
{
navigationController.popoverPresentationController.barButtonItem = popoverSource;
}
else if ( [popoverSource isKindOfClass:[NSArray class]] )
{
NSArray* popoverSourceArray = (NSArray*) popoverSource;
NSParameterAssert(popoverSourceArray.count == 2);
UIView* sourceView = popoverSourceArray[0];
CGRect sourceRect = [(NSValue*) popoverSourceArray[1] CGRectValue];
navigationController.popoverPresentationController.sourceView = sourceView;
navigationController.popoverPresentationController.sourceRect = sourceRect;
}
[self presentViewController:navigationController animated:NO completion:nil];
}
}
}
}
@end
示例用法
在 interface builder 中创建一个 segue,并将其 "Kind" 设置为 Custom,并将其 "Class" 设置为 PushPopoverSegue
。
ViewController.m
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
((PushPopoverSegue*) segue).sourceView = /* source view */;
((PushPopoverSegue*) segue).sourceRect = /* source rect */;
}
-(void) willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
if ( newCollection.horizontalSizeClass == UIUserInterfaceSizeClassUnspecified )
return;
[self transitionPushPopoversToHorizontalSizeClass:newCollection.horizontalSizeClass withMapping:@{
@"MyDestinationViewController": @[ /* source view */,
[NSValue valueWithCGRect:/* source rect*/] ]
}];
}
在我的应用程序中,某些视图控制器在 iPad(或更具体地说,常规水平尺寸 class)上将它们呈现为弹出窗口是有意义的,但在 iPhone(或紧凑的水平尺寸 class)将它们推到导航堆栈上是有意义的。有没有一种优雅的方式来支持这个?默认情况下,如果我使用 "Present as Popover" segue,它将在 iPhone 上模态显示,这不是我想要的。
我找到了一种方法来获得我想要的行为,但它很丑陋而且似乎容易出错。我根据当前大小 class 在两个不同的 segue 之间进行选择。为了支持 iOS 9 多任务处理,我实现了 [UIViewController willTransitionToTraitCollection:withTransitionCoordinator]
并在弹出窗口和弹出窗口之间手动移动视图控制器导航控制器(这部分似乎特别容易出错)。
似乎应该有一些简单的方法来实现自定义 segue 来处理这个问题,或者实现某种自定义自适应表示控制器,但我一直无法理解它。有人成功过吗?
在我看来这是最简单的方法,
第 1 步:创建从一个控制器到另一个控制器的两个 segue。
第 2 步: 将一个 segue 的 segue 属性 设置为另一个
的 push 和 popover
第 3 步: 现在根据您的要求调用执行 segue,i.e.iPad 或 iPhone
示例代码注意:将 bool 条件更改为 false 以检查 didSelectRowAtIndexPath
.
这是我最终构建的内容。我对它不是很满意,这就是为什么我直到现在才发布它。它不支持使用相同 class 查看控制器的两个 segue,并且它需要您自己跟踪弹出窗口的源矩形和源视图。但也许这对其他人来说是一个很好的起点。
PushPopoverSegue.swift
import UIKit
class PushPopoverSegue: UIStoryboardSegue {
var sourceBarButtonItem: UIBarButtonItem!
var permittedArrowDirections: UIPopoverArrowDirection = .Any
override func perform() {
assert( self.sourceViewController.navigationController != nil )
assert( self.sourceBarButtonItem != nil )
if self.sourceViewController.traitCollection.horizontalSizeClass == .Compact {
self.sourceViewController.navigationController!.pushViewController(self.destinationViewController, animated: true)
}
else {
let navigationController = UINavigationController(rootViewController: self.destinationViewController)
let popover = UIPopoverController(contentViewController: navigationController)
popover.presentPopoverFromBarButtonItem(self.sourceBarButtonItem, permittedArrowDirections: self.permittedArrowDirections, animated: true)
}
}
}
UIViewController+PushPopoverTransition.h
#import <UIKit/UIKit.h>
@interface UIViewController (PushPopoverTransition)
- (void) transitionPushPopoversToHorizontalSizeClass: (UIUserInterfaceSizeClass) sizeClass withMapping: (NSDictionary*) mapping;
@end
UIViewController+PushPopoverTransition.m
#import "UIViewController+PushPopoverTransition.h"
@implementation UIViewController (PushPopoverTransition)
- (void) transitionPushPopoversToHorizontalSizeClass: (UIUserInterfaceSizeClass) sizeClass withMapping: (NSDictionary*) mapping
{
if ( sizeClass == UIUserInterfaceSizeClassCompact )
{
if ( self.presentedViewController == nil )
return;
NSParameterAssert( [self.presentedViewController isKindOfClass:[UINavigationController class]] );
UINavigationController* navigationController = (UINavigationController*) self.presentedViewController;
NSArray* viewControllers = navigationController.viewControllers;
UIViewController* topOfStack = viewControllers[0];
if ( [mapping.allKeys containsObject:NSStringFromClass( [topOfStack class] ) ] )
{
[self.presentedViewController dismissViewControllerAnimated:NO completion:^{
for ( UIViewController* viewController in viewControllers )
[self.navigationController pushViewController:viewController animated:NO];
}];
}
}
else if ( sizeClass == UIUserInterfaceSizeClassRegular )
{
NSUInteger indexOfSelf = [self.navigationController.viewControllers indexOfObject:self];
if ( indexOfSelf < self.navigationController.viewControllers.count - 1 )
{
UIViewController* topOfStack = self.navigationController.viewControllers[indexOfSelf + 1];
if ( [mapping.allKeys containsObject:NSStringFromClass( [topOfStack class] )] )
{
NSArray* poppedControllers = [self.navigationController popToViewController:self animated:NO];
UINavigationController* navigationController = [[UINavigationController alloc] init];
navigationController.modalPresentationStyle = UIModalPresentationPopover;
navigationController.viewControllers = poppedControllers;
id popoverSource = mapping[NSStringFromClass( [topOfStack class] )];
if ( [popoverSource isKindOfClass:[UIBarButtonItem class]] )
{
navigationController.popoverPresentationController.barButtonItem = popoverSource;
}
else if ( [popoverSource isKindOfClass:[NSArray class]] )
{
NSArray* popoverSourceArray = (NSArray*) popoverSource;
NSParameterAssert(popoverSourceArray.count == 2);
UIView* sourceView = popoverSourceArray[0];
CGRect sourceRect = [(NSValue*) popoverSourceArray[1] CGRectValue];
navigationController.popoverPresentationController.sourceView = sourceView;
navigationController.popoverPresentationController.sourceRect = sourceRect;
}
[self presentViewController:navigationController animated:NO completion:nil];
}
}
}
}
@end
示例用法
在 interface builder 中创建一个 segue,并将其 "Kind" 设置为 Custom,并将其 "Class" 设置为 PushPopoverSegue
。
ViewController.m
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
((PushPopoverSegue*) segue).sourceView = /* source view */;
((PushPopoverSegue*) segue).sourceRect = /* source rect */;
}
-(void) willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
if ( newCollection.horizontalSizeClass == UIUserInterfaceSizeClassUnspecified )
return;
[self transitionPushPopoversToHorizontalSizeClass:newCollection.horizontalSizeClass withMapping:@{
@"MyDestinationViewController": @[ /* source view */,
[NSValue valueWithCGRect:/* source rect*/] ]
}];
}