使用UIView
动画API和视图控制器包含,当前的Cocoa Touch堆栈非常适合视图控制器之间的自动转换。
我觉得很难写的是视图控制器之间的交互式转换。例如,当我只想使用推送动画将一个视图替换为另一个视图时,我可以使用UINavigationController
或使用包含API并自己编写转换。但是我希望转换是交互式的,触摸控制的很常见:用户开始拖动当前视图,从侧面显示传入视图,并且通过平移触摸手势控制转换。用户可以稍微平移以“窥视”传入视图,然后向后平移,保持当前视图可见。如果手势结束低于某个阈值,则转换将被取消,否则它将完成。
(如果这个不够清楚,我说的是像iBooks中的翻页,但是在不同的视图控制器之间,并推广到任何这样的交互式过渡。)
我知道如何编写这样的转换,但是当前的视图控制器必须对转换了解太多 - 它占用了过多的代码。而且甚至没有提到可以轻松地实现两种不同的交互式转换,在这种情况下,所讨论的控制器充满了与其紧密耦合的转换代码。
是否有抽象的模式,概括交互式转换代码并将其移动到单独的类或代码块中?也许是图书馆,甚至?
答案 0 :(得分:2)
我不知道有任何库可以做到这一点,但是我已经用UIViewController上的类别抽象出了转换代码,或者为我的视图控制器创建了具有转换代码的基类在里面。我在基类中保留了所有混乱的转换代码,在我的控制器中,我只需要添加一个手势识别器并从其action方法中调用基类方法。
-(IBAction)dragInController:(UIPanGestureRecognizer *)sender {
[self dragController:[self.storyboard instantiateViewControllerWithIdentifier:@"GenericVC"] sender:sender];
}
编辑后:
这是我的一次尝试。这是DragIntoBaseVC中的代码,它是另一个控制器需要继承的控制器,使用上面的代码将视图拖入其中。这只处理拖拽(仅从右侧),而不是拖拽(仍在处理拖拽,以及如何使这个方向相对于方向更通用)。很多代码都在那里处理轮换。它适用于任何方向(颠倒除外),适用于iPhone和iPad。我通过动画布局约束而不是设置框架来制作动画,因为这似乎是苹果公司的方向(我怀疑他们将来会贬低旧的支柱和弹簧系统)。
#import "DragIntoBaseVC.h"
@interface DragIntoBaseVC ()
@property (strong,nonatomic) NSLayoutConstraint *leftCon;
@property (strong,nonatomic) UIViewController *incomingVC;
@property (nonatomic) NSInteger w;
@end
@implementation DragIntoBaseVC
static int first = 1;
-(void)dragController:(UIViewController *) incomingVC sender:(UIPanGestureRecognizer *) sender {
if (first) {
self.incomingVC = incomingVC;
UIView *inView = incomingVC.view;
[inView setTranslatesAutoresizingMaskIntoConstraints:NO];
inView.transform = self.view.transform;
[self.view.window addSubview:inView];
self.w = self.view.bounds.size.width;
NSLayoutConstraint *con2;
switch ([UIDevice currentDevice].orientation) {
case 0:
case 1:
self.leftCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeLeft relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.w];
con2 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:inView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
break;
case 3:
self.leftCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:self.w];
con2 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeLeft relatedBy:0 toItem:inView attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
break;
case 4:
self.leftCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:-self.w];
con2 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeRight relatedBy:0 toItem:inView attribute:NSLayoutAttributeRight multiplier:1 constant:0];
break;
default:
break;
}
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeWidth relatedBy:0 toItem:inView attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:0 toItem:inView attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
NSArray *constraints = @[self.leftCon,con2,con3,con4];
[self.view.window addConstraints:constraints];
first = 0;
}
CGPoint translate = [sender translationInView:self.view];
if ([UIDevice currentDevice].orientation == 0 || [UIDevice currentDevice].orientation == 1 || [UIDevice currentDevice].orientation == 3) { // for portrait or landscapeRight
if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged) {
self.leftCon.constant += translate.x;
[sender setTranslation:CGPointZero inView:self.view];
}else if (sender.state == UIGestureRecognizerStateEnded){
if (self.leftCon.constant < self.w/2) {
[self.view removeGestureRecognizer:sender];
[self finishTransition];
}else{
[self abortTransition:1];
}
}
}else{ // for landscapeLeft
if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged) {
self.leftCon.constant -= translate.x;
[sender setTranslation:CGPointZero inView:self.view];
}else if (sender.state == UIGestureRecognizerStateEnded){
if (-self.leftCon.constant < self.w/2) {
[self.view removeGestureRecognizer:sender];
[self finishTransition];
}else{
[self abortTransition:-1];
}
}
}
}
-(void)finishTransition {
self.leftCon.constant = 0;
[UIView animateWithDuration:.3 animations:^{
[self.view.window layoutSubviews];
} completion:^(BOOL finished) {
self.view.window.rootViewController = self.incomingVC;
}];
}
-(void)abortTransition:(int) sign {
self.leftCon.constant = self.w * sign;
[UIView animateWithDuration:.3 animations:^{
[self.view.window layoutSubviews];
} completion:^(BOOL finished) {
[self.incomingVC.view removeFromSuperview]; // this line and the next reset the system back to the inital state.
first = 1;
}];
}
答案 1 :(得分:2)
我觉得有点奇怪试图回答这个问题...就像我只是不理解这个问题一样,因为你无疑比我更了解这一点,但是这里有了。
您使用了包含API并自行编写了转换,但您对结果不满意?到目前为止,我发现它非常有效。我创建了一个没有视图内容的自定义容器视图控制器(将子视图设置为全屏)。我将其设置为rootViewController
。
我的收容夹视图控制器有一堆预先固定的转换(在enum
中指定),每个转换都有预先确定的手势来控制转换。我使用2个手指平移左/右幻灯片,3个手指捏/缩放以进行增长/缩小到屏幕效果的中间和其他几个。有一种设置方法:
- (void)addTransitionTo:(UIViewController *)viewController withTransitionType:(TransitionType)type;
然后我调用方法来设置视图控制器交换输出。
[self.parentViewController addTransitionTo:nextViewController withTransitionType:TransitionTypeSlideLeft];
[self.parentViewController addTransitionTo:previousViewController withTransitionType:TransitionTypeSlideRight];
[self.parentViewController addTransitionTo:infoViewController withTransitionType:TransitionTypeSlideZoom];
父容器为转换类型添加适当的转换手势,并管理视图控制器之间的交互式移动。如果您正在平移并且在中间放开,它将会反弹到覆盖屏幕大部分区域的任何一个。完整转换完成后,容器视图控制器将删除旧视图控制器以及随之而来的所有转换。您还可以使用以下方法随时删除转场:
- (void)removeTransitionForType:(TransitionType)type;
虽然交互式转换很好,但在某些情况下我也想要非交互式转换。我使用了不同的类型,因为我确实有一些只是静态的过渡,因为我不知道哪种手势适合于它们是交互式的(如交叉淡入淡出)。
- (void)transitionTo:(UIViewController *) withStaticTransitionType:(StaticTransitionType)type;
我最初为幻灯片应用程序编写了容器,但从那以后我转过身来在几个应用程序中重复使用它。我还没有将它拉出来重新使用,但这可能只是时间问题。
答案 2 :(得分:2)
这是我到过的API。它有三个组件 - 想要创建到另一个的转换的常规视图控制器,自定义容器视图控制器和转换类。过渡类看起来像这样:
@interface TZInteractiveTransition : NSObject
@property(strong) UIView *fromView;
@property(strong) UIView *toView;
// Usually 0–1 where 0 = just fromView visible and 1 = just toView visible
@property(assign, nonatomic) CGFloat phase;
// YES when the transition is taken far enough to perform the controller switch
@property(assign, readonly, getter = isCommitted) BOOL committed;
- (void) prepareToRun;
- (void) cleanup;
@end
从这个抽象类中,我推导出推动,旋转等具体过渡。大多数工作都在容器控制器中完成(简化了一点):
@interface TZTransitionController : UIViewController
@property(strong, readonly) TZInteractiveTransition *transition;
- (void) startPushingViewController: (TZViewController*) controller withTransition: (TZInteractiveTransition*) transition;
- (void) startPoppingViewControllerWithTransition: (TZInteractiveTransition*) transition;
// This method finishes the transition either to phase = 1 (if committed),
// or to 0 (if cancelled). I use my own helper animation class to step
// through the phase values with a nice easing curve.
- (void) endTransitionWithCompletion: (dispatch_block_t) completion;
@end
为了使事情更清楚,这就是过渡的开始:
- (void) startPushingViewController: (TZViewController*) controller withTransition: (TZInteractiveTransition*) transition
{
NSParameterAssert(controller != nil);
NSParameterAssert([controller parentViewController] == nil);
// 1. Add the new controller as a child using the containment API.
// 2. Add the new controller’s view to [self view].
// 3. Setup the transition:
[self setTransition:transition];
[_transition setFromView:[_currentViewController view]];
[_transition setToView:[controller view]];
[_transition prepareToRun];
[_transition setPhase:0];
}
TZViewController
只是一个简单的UIViewController
子类,它包含一个指向转换控制器的指针(非常类似于navigationController
属性)。我使用类似于UIPanGestureRecognizer
的自定义手势识别器来驱动转换,这是视图控制器中手势回调代码的外观:
- (void) handleForwardPanGesture: (TZPanGestureRecognizer*) gesture
{
TZTransitionController *transitionController = [self transitionController];
switch ([gesture state]) {
case UIGestureRecognizerStateBegan:
[transitionController
startPushingViewController:/* build next view controller */
withTransition:[TZCarouselTransition fromRight]];
break;
case UIGestureRecognizerStateChanged: {
CGPoint translation = [gesture translationInView:[self view]];
CGFloat phase = fabsf(translation.x)/CGRectGetWidth([[self view] bounds]);
[[transitionController transition] setPhase:phase];
break;
}
case UIGestureRecognizerStateEnded: {
[transitionController endTransitionWithCompletion:NULL];
break;
}
default:
break;
}
}
我对结果很满意 - 它非常简单,不使用黑客,很容易扩展新的转换,视图控制器中的代码相当短而且简单。我唯一的抱怨是我必须使用自定义容器控制器,所以我不确定它如何与标准容器和模态控制器一起使用。