Interactive transitions between dispatchers?

With the UIView animation UIView and view controller, the current Cocoa Touch stack is very well suited for automatic transitions between dispatchers.

What is hard for me to write is the interactive transitions between the view controllers. As an example, when I just want to replace one view with another using push animation, I can use the UINavigationController or use the API and write the transition itself. But quite often, when I want the transition to be interactive, with touch controls: the user starts dragging the current view, the incoming view appears from the side, and the transition is controlled by a pan touch gesture. The user can just peep a bit to β€œpeek” at the incoming view, and then put it back, keeping the current view visible. If the gesture ends below a certain level, the transition is canceled, otherwise it ends.

(In case this is not clear enough, I'm talking about something like a page transition on iBooks, but between different view managers it generalizes to any such interactive transition.)

I know how to write such a transition, but the current view controller must know too much about the transition - it takes up too much of its code. And this is not even a mention of the fact that two different interactive transitions are possible, in which case the corresponding controllers are full of transition code, closely related to it.

Is there a template for abstract, generalize the interactive transition code and move it to a separate class or code piece? Maybe a library, even?

+4
source share
3 answers

This is the API I came to. There are three components for it: a regular view controller that wants to create a transition to another, a custom container view controller, and a transition class. The transition class is as follows:

 @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 

From this abstract class I get specific transitions for pushing, spinning, etc. Most of the work is done in the container controller (simplified bit):

 @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 

To make things clearer, here's how the transition begins:

 - (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 is just a simple subclass of UIViewController that contains a pointer to a transition controller (very similar to the navigationController property). I use my own gesture recognizer, similar to UIPanGestureRecognizer , to control the transition, it looks like the gesture callback code in the view controller:

 - (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; } } 

I am pleased with the result - it is quite simple, it does not use hacks, it is easy to extend it with the help of new transitions, and the code in the view controllers is quite short and simple. My only problem is that I have to use a custom container controller, so I'm not sure how this works with standard containers and modal controllers.

+2
source

I don’t know of any libraries to do this, but I highlighted the transition code either with a category on the UIViewController, or created a base class for my view controllers that have the transition code. I save all the messy transition code in the base class, and in my controller I just need to add a gesture recognizer and call the base class method from its action method.

 -(IBAction)dragInController:(UIPanGestureRecognizer *)sender { [self dragController:[self.storyboard instantiateViewControllerWithIdentifier:@"GenericVC"] sender:sender]; } 

After editing:

Here is one of my attempts. This is the code in DragIntoBaseVC, which is the controller that another controller must inherit in order to view it using the code above. This only handles the drag (only to the right), and not the drag (still working on it, and how to make it even more general with respect to the direction). Many of this code is there to handle rotations. It works in any orientation (except the inverted part) and works on both the iPhone and iPad. I am doing animation by animating layout constraints rather than setting frames, as this is similar to how Apple lights up (I suspect that they will depreciate the old layout and spring system in the future).

 #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;  }]; } 
+2
source

I feel a little strange trying to answer it ... as if I just do not understand the question, because you undoubtedly know it better than me, but here it goes.

You use the API and wrote the transition yourself, but are your unsatisfied with the result? So far, I have found this very effective. I created a custom container view controller without the contents of the view (set the child view in full screen). I installed this as my rootViewController .

My containment view controller has a bunch of pre-prepared transitions (specified in enum ), and each transition has a predefined gesture to control the transition. I use 2-finger panning for left / right slides, 3-finger zoom / zoom to zoom in / out to the middle of the screen effect and several others. There is a way to configure:

 - (void)addTransitionTo:(UIViewController *)viewController withTransitionType:(TransitionType)type; 

Then I call the methods to configure the controller summary shutdowns.

 [self.parentViewController addTransitionTo:nextViewController withTransitionType:TransitionTypeSlideLeft]; [self.parentViewController addTransitionTo:previousViewController withTransitionType:TransitionTypeSlideRight]; [self.parentViewController addTransitionTo:infoViewController withTransitionType:TransitionTypeSlideZoom]; 

The parent container adds transition transition gestures for the transition type and controls the interactive movement between view controllers. If you pan, and you let go in the middle, it will return to what covered most of the screen. When the full transition is complete, the container view controller deletes the old view controller and all the transitions that go with it. You can also remove transitions at any time using the method:

 - (void)removeTransitionForType:(TransitionType)type; 

While interactive transitions are good, there are some cases where I also need non-interactive transitions. I use a different type for this, because I have some transitions that are only static, because I don’t know which gesture would be suitable for their interactivity (for example, cross fading).

 - (void)transitionTo:(UIViewController *) withStaticTransitionType:(StaticTransitionType)type; 

I originally wrote a container for the slide column application, but since then I have turned around and reused it in a couple of applications. I have not yet pulled it into the library for reuse, but this is probably only a matter of time.

+2
source

All Articles