Travel update 3: The fall

My last travel update concluded just as I was leaving Rome to head for UIKonf in Berlin; a trip that Apegroup made possible by sponsoring my conference ticket. Since I had already booked a plane from Rome to Prague, I had to take the train the last stretch to Berlin. The timing was supposed to be perfect. I would go straight from the Prague airport to the train station and then arrive in Berlin in enough time to check in at my hostel and go to the UIKonf pre-party. Or so I thought...

IMG_2789.jpg

Of course my train gets severely delayed and I end up an hour late in Berlin. "No problem", I keep thinking. "I'll just go straight to the party and check in my stuff later". So I did. Well there, I meet both old and new acquaintances and we end up drinking a few beers at a Russian bar. Time flies by and as my cab arrives the hostel, I notice something. The windows are dark and the reception door is locked.

Desperately, I try calling a telephone number which I then hear ringing through the door. Without any clue or WiFi in one of the shadier areas of Berlin, next to a drug dealer's nest (Görlitzer Park), I walk around to see if there's anything open. After a while I then realize there's a slim chance that some drunk tourists who already checked in at the hostel, might head back home to sleep. So instead, I resort to stalking people. After a few embarrassing moments I finally see a girl heading straight for the door. Luckily, she doesn't pepper spray me and believes me after I show her my booking confirmation. Since she had a free bed in her room, I stayed there and it later turned out to be the exact bed I was booked for anyway.


The next day UIKonf began, dubbed "The independent conference for serious iOS developers". Even though it only started in 2013, organized by Chris Eidhof of objc.io, it has quickly become a high-profile European event in the community. I originally planned to write a dedicated blog post about it, since it was so great. But as you will soon find out, unforeseen circumstances came in the way.

Depending on how you see it, UIKonf lasted two to four days. The day before the actual conference, there were several workshops you could attend with topics ranging from Core Data to Microsoft Azure and UI-design. After the two-day conference, there was also a hack day which spawned incredibly useful tools like Pod Roulette and gave people mind-blowing insights on the Berlin Metro ticket system.

The conference itself was hosted at Heimathafen Neukölln, a gorgeous theater whose name took me the entire week to pronounce properly. Between the presentations, you had your usual coffee breaks. Except the coffee came from Berlin's best coffee shop and was of a different mix every time they served. That level of detail was found throughout the conference. Even the badges were uniquely etched in wooden plates.

IMG_2800.jpg

The talks had well varied topics with a higher focus on practical use than the many inspirational talks at NSConference. The sheer quality of content and presentation of the talks at UIKonf really surprised me. A rising theme among the speakers was to use custom, interactive presentation apps instead of the usual Keynote (or Chris' hot new Deckset). The animation guru David Rönnqvist started this off with a gorgeous, fully animated SceneKit app to explain how OpenGL works on the iOS platform. Then Nick Lockwood took over by doing realtime image loading benchmarks on a custom iPad app, containing all slides and everything. These guys really set the bar and I hope we will see more interactive presentations from here on.

On thursday night, the conference concluded with a party hosted by Facebook, who recently impressed the entire community with Paper and all the great open source projects they've released. Waking up on friday, I felt like shit. Thinking it was a regular hangover, I went to the gym to sweat out the last drops of alcohol in my body before going to Betahaus to hack. When I later crashed at my hostel dorm, I could feel that I was on the brink of getting really, really ill. Just as I was going to fall asleep, a bunch of girls enter the dorm, cheering, all ready for a night out. And of course, my stupid self gets convinced by them to join on their nightly quest.

The Berlin nightlife is famous for the underground techno scene and its notorious bouncers who reject party-going tourists like flies. We ended up with around 10 people trying to get in to some of the more prominent clubs. In Berlin, you can't just go in as a group. They're to cool for that. What you instead do is split up into pairs and spread throughout the lines so the bouncers don't get suspicious. While some make it in, others don't, seemingly random.

IMG_2804.jpg

After a great night out, I woke up feeling even more shitty than the day before. Thinking my hangovers just stacked, I once again went to the gym (worst decision EVER). When neither that or eating a solid meal didn't help, I started to gain suspicion that I might have caught something.


Taking the train back to Prague, I knew for sure I was having a bad throat infection. As beautiful and lively of a city Prague is, I couldn't enjoy it much and just rested as much as possible. Before I could tell, my days in Prague were over and my condition was only getting worse.

Budapest was a city that really made an impact on me even though my condition was progressing towards abysmal by the hour during the entire stay. It has incredible beauty, lots of stuff to do, great people and awesome nightlife. Since this was my last stay abroad before chilling heading back to Stockholm in wait for WWDC, I booked the craziest party hostel I could find together with Chad, the guy I went hiking in Morocco with. This idea turned up to be both great and disastrous at the same time. Retox wasn't your run-of-the-mill backpacker hostel. The partying there happened from mid-day till dawn, every single day. In fact, when we arrived, the staff warned us that their neighbors hate them so much, they sometimes throw buckets of shit on bypassing guests. ¯\(°_o)/¯

On the first night we met Tyler. Tyler was an American guy who was getting married in Slovenia a couple of days later and was on Retox for his bachelor party... except he was alone. By some weird force in the universe, all his friends who were invited had different mishaps happen to them, which made them unable to come. So there were me and Chad, his new stag's mates. Later on, Chad even joined in on Tyler's wedding in Slovenia and stood on his side as a groomsman.

My life in Budapest consisted of three things: partying, curing the hangover from last day and abuse painkillers to stay afloat. My throat was so bad at this point that I had to take cough drops every hour just to be able to talk. Alcohol and four hours of sleep a night didn't exactly help but there was no possible way of avoiding the parties. One of the more remarkable events was the sparty, set in the Szechenyi Baths outdoor spa. Imagine a hot spring pool, a DJ, lots of tourists and alcohol. How could you go wrong?

20140524_232818.jpg

After all the craziness in Budapest, I was ready to drop dead. Arriving in Stockholm I went straight to the doctor, who took one look at me and concluded I was in dire need of penicillin. I looked forward to meet all my friends in Stockholm again, but that was outright impossible in my then current state. I recovered as best I could for a couple of days and then, it was time for San Fransisco and my very first Apple World Wide Developer Conference :D

Travel update 2: The rise

A long time has gone and suddenly there is less than two months left on my journey. The last months have brought several literal and metaphorical ups and and downs. What strikes me over and over though, is how many amazing people I have met and how each new person takes me by surprise. The experience has broadened my mind, to say the least. Without falling further into delirium, here's the story.


Last time I did a proper travel update, I was on my way to Morocco from Spain. I ended up in Marrakesh, "The Red City". Visiting an Arabic country for the first time, Morocco was a brand new world unlike anything I've experienced as a westerner. Although it took a while for the culture shock to settle, I later fell in love with the place. The atmosphere was so different from any European city, I've never felt safer walking down the street in the middle of the night.

As it happened to be, Chad – an American guy I met at the Castle Rock hostel in Edinburgh a month before, was staying in Marrakesh at the same time as me. After meeting up, he proposed to hike up Toubkal, which at 4 167m was the highest peak in the Atlas mountains. Since I'd never hiked in my entire life before, how could I say no?

To get on top of Toubkal, we first had to take a cab to Imlil, the last outpost where cars could get through. Since we arrived late, we stayed at a local bed & breakfast where we could also rent equipment. From there, it was a grueling 6 hour hike past goats and killer mules to the refuge camp below the peak. While I was staying the night inside the refuge, Chad and his newfound friend Matthieu (who he had met on the ferry to Tangier) tented outside.

Matthieu was a French guy who took budget traveling to an extreme. He always carried a tent and hitchhiked wherever he went, even when in the cold vastness of Iceland. All that hitchhiking had him built up his cardio, which made us feel like bulldogs with astma as he raced on in front of us.

After a freezing night at the refuge, it was time to climb up the peak. Conveniently enough, the entire trail was clearly visible on Google Maps, which made pathfinding a no-brainer. It took us roughly two hours through rocks and scree to get up and when finally there, the view and feeling of accomplishment was incredible!

IMG_2697.jpg

On the way down we found some snow patches to have fun in. Having a snowball fight after hiking through the scorching sun in over 35°C was as surreal as it gets. During the final stretch before reaching camp, we walked past a group of 16-17 year old kids and slid down the final bits of snow. Just five minutes later as we were arriving at the camp, we noticed something had gone very wrong. People ran past us with a stretcher and then we got the news: One of the kids we just passed, slid down the same patch of snow as us but lost control. On-site doctors had a look and concluded the boy had fractures in his spine and skull and an emergency helicopter was called for. We never found out how the kid did, but it surely bummed out the hike back home.


I spent a couple of more lazy days in Marrakesh (trying to get rid of the blisters on my feet), before continuing my journey. At this point, I didn't have much planned in terms of where to go when. The iOS developer conference UIKonf was drawing near in Berlin but I figured I wouldn't afford after winning the WWDC lottery. I knew I wanted to go to Prague, so I planned my itinerary accordingly. After finding a ridiculously cheap (25€) flight ticket from Rome to Prague, i figured I had to somehow get to Rome from Marrakesh where I was staying. Turns out the cheapest alternative was to stay two days in Madrid.

IMG_2752.jpg

Arriving to Madrid, I didn't really know what to expect. I love Spanish food, culture and climate but Spain had always been associated to beach life for me. In Madrid, of course, there were no beaches to be found. A couple of days earlier, I got an email from Fransisco Sevillano who read my Fluent Pagination post after it was featured in iOS Dev Weekly. He had checked my route, noticed I was in Madrid for a few days and invited me for lunch! I was once again struck by how many incredibly nice people there are in this community.

After several delicious Spanish tortillas and a great conversation, Fransisco told me about an NSCoder presenter night happening later that night in Madrid. He couldn't go himself so I thanked for the lunch and went on my own. The topic for the presentation was ReactiveCocoa. What surprised me though, was that the entire presentation was in Spanish. After spending several weeks in Spain, my Spanish was good enough for me to understand the presentation. Asking questions was more difficult. One of the organizers ended up translating my questions from English to Spanish, while the presenter answered me back in Spanish. Weirdest Q & A session I've experienced, but we all succeeded to communicate with each other in the end.

The next day, I had a session at the huge and super modern FitUp gym, which I highly recommend! At this point, I hadn't been to a gym since all the way back in London a month before. Calisthenics may be great, but I missed lifting iron. After an obligatory bar crawl to check the pulse of the city, my last night was over and it was time to head for Rome. All in all, I left with a much greater appreciation of Madrid than I ever expected.


Roman culture and history has always fascinated me. I was hooked by everything from Rome: Total War and the Spartacus series to Monty Python's Life of Brian. Actually walking among the old ruins and inspecting Colosseum in its full glory was immense.

The AirBnB place I got was in Santa Marinella, a tiny coastal town just an hour away by train. My host was so alike me, it was like stumbling upon a lost twin brother! He was born in St. Petersburg, just like me, moved away when he was six, as I did, a programmer (check!) and had a big interest in fitness (yup).

IMG_2786.jpg

In short I had a very relaxing, although not that exciting stay in Italy. At this time, I had also talked to my boss at Apegroup about UIKonf and how I wanted to go but couldn't afford the conference ticket. Lucky for me, they were awesome enough to sponsor me, even though I was on a leave! With these news, I instantly booked a hostel in Berlin and train tickets from Prague. UIKonf was going to happen and I was super excited!

Interactive Custom Container View Controller Transitions

In issue #12 of the excellent objc.io publication, Joachim Bondo wrote an article about using the view controller transitions API introduced in iOS 7, for supporting animated transitions in a custom container view controller. He concluded with the following words:

Note that we only support non-interactive transitions so far. The next step is supporting interactive transitions as well.

I will leave that as an exercise for you. It is somewhat more complex because we are basically mimicking the framework behavior, which is all guesswork, really.

In this article we will do that exercise, based on the same container view controller Joachim left for us at GitHub.

If you are not familiar with the APIs for view controller transitions, I would suggest you start by reading Joachims article and its many, linked resources. Also, note that even though all the API protocols seem to be safe to implement on our own, we will during this exercise find out that is not really the case. No need to worry though, there is always a way.

I spent most of my weekend trying to swizzle alloc, but then giving up and just solving it in raw assembly – Peter Steinberger

Setting the stage

stage-3.gif

Starting off from Joachims article, we are left with a ContainerViewController catering three child view controllers. The current UI for switching between the child view controllers consists of a range of numbered buttons.

When tapping one of the buttons, a PrivateTransitionContext object, conforming to the UIViewControllerContextTransitioning protocol is created. It holds state for the transition to the new child view controller.

Through the ContainerViewControllerDelegate protocol, a UIViewControllerAnimatedTransitioning animator object is fed. It is responsible for executing a pretty animation, conveying the transition to the user. If the delegate does not return an animator, the default PrivateAnimatedTransition is instanciated.

Finally, the ContainerViewController itself cleans up child view controller relations when the animation is over.

The challenge

Our mission, should we choose to accept it; is to provide an interactive transitioning between the child view controllers in the container.

Since the default transition animation is a horizontal panning, a sensible interaction would be a panning touch gesture across the screen. Users should also be able to cancel an ongoing interactive transition if they wish.

In addition to the default interaction, we should extend the ContainerViewControllerDelegate protocol so that other interactive transitions can be used. While different types of panning or pinching gestures are the most common inputs for interactive transitions, one could with a bit of creativity drive transitions using accelerometer data, button mashing, sound or even camera feeds.

Let's code

Just as in Joachims article, we will implement these new interactions in a few stages, namely four.

The XCode project, with tags marking each stage, can be found on GitHub.

Stage 4: Adding a gesture recognizer

First, we prepare the interaction by adding a new pan gesture recognizer to the container view. This is done by the new PanGestureInteractiveTransition object, which at this stage only setups the gesture recognizer and lets the creator issue a block when a pan gesture begins recognizing.

__weak typeof(self) wself = self;
self.defaultInteractionController = [[PanGestureInteractiveTransition alloc]
  initWithGestureRecognizerInView:self.privateContainerView 
  recognizedBlock:^(UIPanGestureRecognizer *recognizer) {
  
    BOOL leftToRight = [recognizer velocityInView:recognizer.view].x > 0;
    
    NSUInteger currentVCIndex = [self.viewControllers indexOfObject:self.selectedViewController];
    if (!leftToRight && currentVCIndex != self.viewControllers.count-1) {
        NSUInteger newIndex = currentVCIndex+1;
        [wself setSelectedViewController:self.viewControllers[newIndex]];
    } else if (leftToRight && currentVCIndex > 0) {
        NSUInteger newIndex = currentVCIndex-1;
        [wself setSelectedViewController:self.viewControllers[newIndex]];
    }
}];

With these few lines of code, we can trigger a transition to go to the next or previous child view controller when the user starts panning. At this point the transition itself is still non-interactive though.

stage-4.gif

Check out the stage-4 tag and its diff to see the changes compared to the original project.

Stage 5: Making it interactive

In order to make the transition interactive, we need to have an object conforming to the UIViewControllerInteractiveTransitioning protocol. It is a simple protocol containing only three methods:

- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext;
- (UIViewAnimationCurve)completionCurve;
- (CGFloat)completionSpeed;

At the start of a transition, the ContainerViewController checks if the transition should be interactive and either calls startInteractiveTransition: on the interaction controller, or animateTransition: on the animation controller.

id<UIViewControllerInteractiveTransitioning> interactionController = [self _interactionControllerForAnimator:animator];
transitionContext.interactive = (interactionController != nil);
    
if ([transitionContext isInteractive]) {
    [interactionController startInteractiveTransition:transitionContext];
} else {
    [animator animateTransition:transitionContext];
}

In this case, we want the animation executed by the animation controller to be drived by the interaction controller. Now you might wonder how that is done. The answer is (usually) UIPercentDrivenInteractionController. This is what we would have used to achieve the same effect on a UINavigationController or a UITabBarController. Let's try!

Tying animation and interaction together

UIPercentDrivenInteractionController is an object conforming to the UIViewControllerInteractiveTransitioning protocol, providing an easy way to control whichever animated transition that gets thrown at it, as long as it animates through the UIView or CoreAnimation APIs.

In addition to the the protocol implementation, it has only a couple more methods which are used to report progress on the transition.

- (void)updateInteractiveTransition:(CGFloat)percentComplete;
- (void)cancelInteractiveTransition;
- (void)finishInteractiveTransition;

With these methods and some Core Animation magic, the interaction controller controls the flow of the animated transition.

Now we already have our PanGestureInteractiveTransition that holds a pan gesture recognizer. By making it a subclass of UIPercentDrivenInteractionController, we can use that object as our interaction controller and let it report progress up the class hierarchy when we're panning.

- (void)pan:(UIPanGestureRecognizer*)recognizer {

    if (recognizer.state == UIGestureRecognizerStateBegan) {
        self.gestureRecognizedBlock(recognizer);
    } else if (recognizer.state == UIGestureRecognizerStateChanged) {
        CGPoint translation = [recognizer translationInView:recognizer.view];
        CGFloat d = fabs(translation.x / CGRectGetWidth(recognizer.view.bounds));
        [self updateInteractiveTransition:d*0.5];
    } else if (recognizer.state >= UIGestureRecognizerStateEnded) {
        [self finishInteractiveTransition];
    }
}

Once we have the PanGestureInteractiveTransition set up, we can use it as the interaction controller for the transition.

- (id<UIViewControllerInteractiveTransitioning>)
_interactionControllerForAnimator:
(id<UIViewControllerAnimatedTransitioning>)animationController {
    
    if (self.defaultInteractionController.recognizer.state == UIGestureRecognizerStateBegan) {
        return self.defaultInteractionController;
    } else {
        return nil;
    }
}

If you've been paying attention up to this point, you might still wonder how and when the actual animation controller gets fired. Because, when we have an interaction controller, we no longer call the animateTransition: method that initiates the animation.

Firing the simulator, we actually find out that Apple is cheating a bit.

[PrivateTransitionContext _animator]: unrecognized selector sent to instance 0x8e27f10'

// Call stack
...
5 UIKit 0x0098bcae -[UIPercentDrivenInteractiveTransition startInteractiveTransition:] + 56
6 Transitions 0x0000b76c -[PanGestureInteractiveTransition startInteractiveTransition:] + 108
...

As soon as we start panning the app crashes. Looking down the call stack, we can see that UIPercentDrivenInteractiveTransition looks for the animator in our context, calling an undocumented method. Conveniently enough, all Apple-made transition contexts implement this method and that's how the percent driven transition can fire the animation. Unfortunately, that means we cannot use the class for our own custom container view controllers.

Enter AWPercentDrivenInteractionController – a drop-in replacement which is fully API-compliant unlike Apple's own implementation. By changing the superclass of our interaction controller to AWPercentDrivenInteractionController and adding one extra line in the setup, we have a working, interactive transition.

self.defaultInteractionController.animator = animationController;
stage-5.gif

At this stage, we have interactive transitions that work but without cancel support. To see the complete code for this stage, checkout the stage-5 tag or view the diff to see the changes.

If you are interested in the inner workings of AWPercentDrivenInteractionController, look at Appendix A below, or in its separate project on GitHub. The class is also released as a cocoapod with the same name.

Stage 6: Abort!

When users start an interactive transition, they expect being able to cancel it. Just as they can with the pop transition in a navigation controller, we will now make sure they can with ours too.

To enable cancellation, we need to do a bit of work both with our context and the interaction controller. Since all of this already is supported through UIKits protocols, it is not too hard to figure out how to implement. We start off with the changes in PanGestureInteractionController.

At the moment, the interaction controller always calls finishInteractiveTransition when the pan gesture is ended. We can use different criterias, including velocity of the touch, to take the decision on either canceling or finishing the transition. For this example, we just use a simple progress check.

} else if (recognizer.state >= UIGestureRecognizerStateEnded) {
    if (self.percentComplete > 0.2) {
        [self finishInteractiveTransition];
    } else {
        [self cancelInteractiveTransition];
    }
}

When we run cancelInteractiveTransition, AWPercentDrivenInteractionController forwards the message to our transition context. So let's implement the callback in PrivateTransitionContext.

First of all, we add a new Bool property to replace the hardcoded transitionWasCancelled method which always returns NO. We do this in the class extension, since the public getter is already declared by the UIViewControllerContextTransitioning protocol.

@property (nonatomic, assign) BOOL transitionWasCancelled;

Then we implement the two callback stubs that get called by the interactive transition.

- (void)finishInteractiveTransition {self.transitionWasCancelled = NO;}
- (void)cancelInteractiveTransition {self.transitionWasCancelled = YES;}

Remember that the transition context is just a holder of state, it does not actually do much of its own. We now have to check the state in the completion block ContainerViewController defines and react accordingly.

transitionContext.completionBlock = ^(BOOL didComplete) {

    if (didComplete) {
        [fromViewController.view removeFromSuperview];
        [fromViewController removeFromParentViewController];
        [toViewController didMoveToParentViewController:self];
        [self _finishTransitionToChildViewController:toViewController];

    } else {
        [toViewController.view removeFromSuperview];
    }

    if ([animator respondsToSelector:@selector (animationEnded:)]) {
        [animator animationEnded:didComplete];
    }
    self.privateButtonsView.userInteractionEnabled = YES;
};

Another small detail is that we now need to hold on updating the selection states of our navigational buttons before an interactive transition actually has completed. You will find that this change is implemented in the code with the stage-6 tag. To see what change since stage 5, check the diff.

In the best of worlds, we would let the buttons animate along with the transition. That will be left as the final exercise for the reader. In essense, one would use the updateInteractiveTransition: callback on the context to drive the animation forward.

stage-6.gif

Stage 7: Delegation

In this final stage, we will enable delegation of interaction controllers through the ContainerViewControllerDelegate protocol. Just as we already do with animation controllers. We add a third delegate method to the protocol:

- (id <UIViewControllerInteractiveTransitioning>)containerViewController:(ContainerViewController *)containerViewController
                                 interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController;

The common pattern among Apple's implementations is that the interaction controller is dependent on the animation controller, but not the other way around. If the delegate returns an animation controller, it gets the chance to provide an interaction controller too. But it cannot ever control the default animation, since that may change at any notice. Here is a table that shows the possible combinations:

Let's implement this pattern in our ContainerViewController:

- (id<UIViewControllerInteractiveTransitioning>)
_interactionControllerForAnimator:(id<UIViewControllerAnimatedTransitioning>)animationController
animatorIsDefault:(BOOL)animatorIsDefault {
    
    if (self.defaultInteractionController.recognizer.state == UIGestureRecognizerStateBegan) {
        self.defaultInteractionController.animator = animationController;
        return self.defaultInteractionController;
    } else if (!animatorIsDefault && [self.delegate respondsToSelector:@selector(containerViewController:interactionControllerForAnimationController:)]) {
        return [self.delegate containerViewController:self interactionControllerForAnimationController:animationController];
    } else {
        return nil;
    }
}

There is only one step left until our API is complete for delegating interactive transitions. If someone uses our ContainerViewController and wants to vend their entire own interactive transitions, they probably want to disable the interaction controller already built in. We look at UINavigationController for inspiration:

@property (nonatomic, readonly) UIGestureRecognizer *interactiveTransitionGestureRecognizer;

It is a simple solution to expose the gesture recognizer that triggers the default interaction transition through a readonly-property. Since UIGestureRecognizer has an enabled property, anyone using our class can just disable the recognizer to disable the default interactive transition.

Since UIGestureRecognizer has an enabled property, anyone using our class can just disable the recognizer to disable the default interactive transition. Note also that we in the public interface cast it as a plain UIGestureRecognizer without specifying that it recognizes a pan gesture. This is the highest, useful level of abstraction. Remember: whenever possible, code against an interface, not an implementation.

Now our ContainerViewController is fully catering all needs for both custom animated AND interactive transitions. With this separation of concerns, the container view becomes incredibly powerful as transitions can change dynamically while the app is running, just from our public API.

The final code for this project can be found at the stage-7 tag. Just as before, there is also a diff against the previous stage that you might be interested in.

To test the delegation of interaction controllers, there is a small, fake interaction class that drives the interaction entirely by itself inside the app delegate. By un-commenting the lines setting the delegate and disabling the default interaction, you can see it in action by pressing any of the transition buttons.

stage-7.gif

Conclusion

In this article we have learned how to implement the iOS 7 view controller transitions API for adding interactive transitions to a custom container view controller. In iOS 7-land, this is expected of all general containers and now we have done our part.

The newly introduced view controller transitions API has its rough edges, something that becomes apparent when trying to implement interactive transitions with the framework-provided UIPercentDrivenInteractiveTransition. Perhaps this is something that will be remedied in the next major release of iOS. With just days away from Apple's World Wide Developer Conference, we may soon find out.

Even though we worked long and hard, there is still one piece of the transitions API still missing: the UIViewControllerTransitionCoordinator. This is a protocol that enables other animations alongside a view controller transition.

As mentioned earlier, a nice thing would be to animate the state of the buttons as a transition progresses. This could be done by using the transition coordinator. I invite you to take that challenge :)

Appendix A – AWPercentDrivenInteractionController

AWPercentDrivenInteractiveTransition is a drop-in replacement for UIPercentDrivenInteractiveTransition for use in custom container view controllers.

Why do you need it? Because Apples own UIPercentDrivenInteractiveTransition calls undocumented methods on your custom UIViewControllerContextTransitioning objects.

Note that this class can also be used with UIKits standard container view controllers such as UINavigationController, UITabBarController and for presenting modal view controllers.

Inner mechanics

The percent driven interaction controller works by taking advantage of that CALayer (which drives UIView) implement the CAMediaTiming protocol.

- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    
    _transitionContext = transitionContext;
    [_transitionContext containerView].layer.speed = 0;
    
    [_animator animateTransition:_transitionContext];
}

When an interactive transition starts, AWPercentDrivenInteractionController freezes all animations inside the transition contexts container view by setting its layers speed to 0. It then calls the animation controllers animateTransition: method, which adds the transition animations to the layer.

The interactive transition object is now in full control of all the animations residing in the container view.

- (void)updateInteractiveTransition:(CGFloat)percentComplete {
    self.percentComplete = fmaxf(fminf(percentComplete, 1), 0); // Input validation
}
- (void)setPercentComplete:(CGFloat)percentComplete {

    _percentComplete = percentComplete;

    [self _setTimeOffset:percentComplete*[self duration]];
    [_transitionContext updateInteractiveTransition:percentComplete];
}
- (void)_setTimeOffset:(NSTimeInterval)timeOffset {
    [_transitionContext containerView].layer.timeOffset = timeOffset;
}

To make interactions drive the animation forward, the timeOffset property on the container view layer is adjusted during interaction callbacks. This renders the layer as if it was in mid-animation even though all animations are technically paused.

Since animation is controlled by timing, the animation curve used is very important for the resulting feel of the interaction. When aiming for direct manipulation, use UIViewAnimationCurveLinear in your animated transition.

Finishing

The mechanics of finishing an interaction is very simple. Basically, the layers speed is reset to 1.0, beginTime is set to the old timeOffset and timeOffset is reset to 0. This makes the animation complete just as it usually would, beginning at the same point in time as we rendered before the interaction was completed.

- (void)finishInteractiveTransition {
    CALayer *layer = [_transitionContext containerView].layer;

    layer.speed = 1.0;

    CFTimeInterval pausedTime = [layer timeOffset];
    layer.timeOffset = 0;
    layer.beginTime = 0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;

    [_transitionContext finishInteractiveTransition];
}

We are also relying on the animator object to call completeTransition: on the transition context once the animation is completed.

Cancelling

Cancelling transitions turned out to be somewhat more complicated. Even though it should be possible to set a layer's speed property to -1.0, I never got this to work. Instead it is done the old-fashioned way by using a CADisplayLink:

- (void)cancelInteractiveTransition {

    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_tickCancelAnimation)];
    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

    [_transitionContext cancelInteractiveTransition];
}

As the display link ticks, the layer's timeOffset gets adjusted negatively until it finally reaches 0.

- (void)_tickCancelAnimation {

    NSTimeInterval timeOffset = [self _timeOffset]-[_displayLink duration];
    if (timeOffset < 0) {
        [self _transitionFinishedCanceling];
    } else {
        [self _setTimeOffset:timeOffset];
    }
}

When that happens, we invalidate the display link and reset the layer's speed. This also triggers the completion block inside the animator, which should tell the context that the transition is over.

- (void)_transitionFinishedCanceling {
    [_displayLink invalidate];

    CALayer *layer = [_transitionContext containerView].layer;
    layer.speed = 1;
}

For an in-depth view of what can be done with the CAMediaTiming protocol, check out this blogpost by my fellow Swede, the animations expert David Rönnqvist.

How to get

Check out the source at GitHub or install as a CocoaPod by adding pod 'AWPercentDrivenInteractiveTransition' to your podfile.

Málaga to Marrakech: 24 hours of travel

11:30: Waking up after a night of drinking games with great company.

12:00: Walking to the beach to get one last dip in the Mediterranean Sea.

13:30: Catching the bus to Tarifa after running through the city in usual manner.

16:00: Arriving in Tarifa, meeting an American family and walking from the bus stop to the port.

17:00 (GMT +1): Getting onboard the katamaran to Tangier. Quite a rough ride, glad my motion sickness pills from the bus are still in effect.

17:00 (GMT): Landing in Tangier. Sharing a cab with an Estonian stewardess from the ferry company who recommends me to buy first class tickets for the train to Marrakech, "Second class is dangerous".

17:30: Buying tickets at the train station. After first thinking my train would leave in just one hour, began realizing I just switched time zones and was mistaken about the train departure time. 3 hours to kill.

17:30: Walking at the beach. "Friendly" guy starts talking to me, shows me around. Insists on showing the local brothels, gets disappointed when I tell him I'm not interested.

18:00: Arriving in the city center. "Friendly" guy asks for a tip of 20 dirhams, not much considering the long walk. Taking a shawarma plate to still my hunger.

19:00: Wandering around in Tangier, taking note of several locals telling me "Where are you going? There are no hotels this way".

20:00: Overrated my sense of direction. No cached maps on my iPhone, just the compass. Checking the compass, realized I've been going the completely wrong direction all the time.

21:15: Back at the train station, boarding the train. Inspecting my "First class" cabin.

08:00: Waking up in the cabin, arriving in Marrakech. Grabbing a glass of orange juice and a chicken panini for breakfast and WiFi. Checking walking directions for the Equity Point hostel and caching up maps.

08:30: Begin walking towards the hostel, ignoring directions. "Pff, doesn't look that hard on Google Maps".

10:00: Arriving on the spot I thought the hostel was located at, none to be found. Starts thinking of backtracking to the point where I could follow walking directions.

10:15: Asking a local for directions. Good and bad idea. Turns out there's two streets with the same name, I was at the wrong one. Follows local who tells me to walk 100 metres behind him, else he could get in jail. Fishy enough.

10:30: Arriving at Equity Point hostel. Slips a 20 dirham tip, just as in Tangier. Turns out this local is a bit more greedy, telling me he usually gets 400 dirhams from American families (~40 €). Local gets upset, demanding at least 100 dirhams. Finally, gives him 50 dirhams to avoid trouble.

10:45: 3 hours to kill until check-in. Tries out wifi, turns out it sucks. Starts reading Javascript: The Good Parts by the pool.

A day trip to Guadalest valley

Today I put my imaginary tourist hat on and went on a day trip to the Guadalest valley, ranked no 1 of all the attractions in the Alicante area on Tripadvisor.

Of course I knew this was a tourist trap, but my dear AirBnB host had begun hinting that I should do something else than programming and going to the beach all day. And to be honest, it looked kind of nice.

To get to the valley I had to take the tram to the neighboring, tourist-flooded city Benidorm and then a public bus. This bus only went once a day so I needed to have precise timing in order to not miss in.

In my usual manner I calmly ate my breakfast for way too long and ended up running 2K, catching the tram thanks to a well timed red light with 5 seconds to spare. Perfect timing.

On the bus, I noticed how I was the only one younger than 60, except for one chinese girl who seemed to avoid any social contact no matter what. I didn't care much though, since I enjoy exploring things alone anyway.

Well at the valley, the tourist trap was even worse than I thought. The place was crawling with fat, elderly people, eating crap food for outrageuos prices. The views were sick though!

Looking out from the the established view point, I noticed a distant ruined tower on top of a rock. It looked like it wouldn't be a problem climbing it. Since there wasn't anything to do in the village anyway, I went for a little adventure.

IMG_2466.jpg

After approaching the rock from a few different angles with no good path to be found I encountered a man with a donkey. Asking him about the way in my crackling Spanish, he showed me a hidden path between some rocks on the cliff-side.

Hidden path between the rocks

Hidden path between the rocks

The path was way steeper than I imagined.

The path was way steeper than I imagined.

I had to fight my way through thorn bushes and past snakes to get to the top, but standing on top of the wall with a view of the entire valley, knowing that envy tourists were probably wondering what crazy person I was from the other side, made the entire trip worth it.

View from top of the tower walls

View from top of the tower walls

All in all the roundtrip cost me about 12€ + 2:50€ for the most expensive popsicle ever. Well worth it, as long as you stay away from the crowd.

Travel update: One month in – aka I'm not dead yet

One month have gone and I've been absolutely terrible at keeping the blog updated. Getting stuff done has in general been extremely hard since it takes a long time to create routines while constantly traveling. Every time I go to a new place, I need to find out how to get around, somewhere to work (proven to be hard), a grocery store, a good enough gym, supplements store, etc. Also, although hostels are cheap and you meet great people, everybody is on holiday and you get constantly tempted to go out partying.

After many rough weeks with long nights and bad habits, I've finally calmed down staying in a beautiful AirBnB house near the beach in Alicante. My hopes are that I will be able to become productive and live a good life here for a while.

Playa de San Juan, Aicante, 5 minutes walk from my current stay

Playa de San Juan, Aicante, 5 minutes walk from my current stay

Anyhow, since that's a different story, here's a recap of what I've actually been doing since NSConference in Leicester.

The Athens of the North

Edinburgh Castle, as seen just below Castle Rock Hostel

Edinburgh Castle, as seen just below Castle Rock Hostel

Straight after NSConf, I was going to Edinburgh to visit friends and meet up with some Scottish devs. The night before I had a chat with a friend and coworker who was doing her master's studies in Edinburgh about where I was staying. With 30 minutes left to cancel my previous booking, she convinced me to instead book the Castle Rock hostel. Since it was the first hostel I've ever stayed in, I didn't know what to expect. But now when I've been to a couple of ones, nothing beats it. Everybody was super friendly, the staff lived at the hostel themselves and they arranged awesome pub crawls and parties.

"Paint me like one of your french girls"

"Paint me like one of your french girls"

One of these events was a weekly beer pong tournament, a first for me. Everybody was randomly arranged in teams and I ended up with a New Yorkian guy we dubbed Captain America. After failing miserably the first match with my team mate having to cover for me, I got the hang of it as we progressed in the bracket. Winning with more and more margin, we ended up as champions and got to enjoy the spoils of victory at a nearby club. :D

Anticipation is high for the beer pong tournament at Castle Rock

Anticipation is high for the beer pong tournament at Castle Rock

It wasn't all partying in Edinburgh. I also attended some meetups that Marius Ciocanel told me about. There I met Markos 'qnoid' Charatzas who earlier gave an interesting speak about sound debugging at NSConf and Mike McQuaid from GitHub. One meetup I particularly enjoyed was the Product Tank, hosted in Skyscanners office next to the gym I went to. It was a first time for me heading to a product management meetup and I found it super interesting to discuss with people that have other perspectives.

Debate panel at the Product Tank meetup in Edinburgh, me on the right

Debate panel at the Product Tank meetup in Edinburgh, me on the right

All in all I really enjoyed Edinburgh. Everybody was super friendly, there was lots of tech people and the views were magnificent.

Casual programming from Arthur's seat volcano in Edinburgh

Casual programming from Arthur's seat volcano in Edinburgh

London

Kneeing Adolf in the nuts at Madame Tussauds, London

Kneeing Adolf in the nuts at Madame Tussauds, London

Street lunch in London

Street lunch in London

After Edinburgh I went on to London to meet up with a bunch of people. For the first few days, my mom came from Sweden to visit me and do some touristing. So after that I went on visiting the new Apegroup London office and going to some meetups. Sadly I missed out on the London iOS Developer Group with Dave Verwer (curator of the awesome iOS Dev Weekly newsletter), among others. Without knowing anybody, I signed up and went to the bar where the meet was. Turns out the bar was pretty big and I didn't recognize anyone. So after looking around for a while and trying to get hold of people through Twitter, I gave up.

Besides surprising my colleagues back in Sweden by turning up for the weekly meeting (with the London office Skypeing in), not that much exciting actually happened in London. I really wanted to go for Startup Weekend but unfortunately I was way too late with my application.

Also, I never would have thought it, but I found London way to big for my taste. Wherever you went it was at least a 30 minute commute, even within the centre of the city.

Appsterdam

Moving on to Amsterdam, I stayed right in the centre of town at the Flying Pig uptown hostel. Within 10 minutes I had found two drinking buddies and we went on to roam the night. The nightlife was awesome with every bar having a good DJ and dancefloor, even went it wasn't a proper club.

A Swedish app maker, an American actress and a Brazilian DJ, out to make the Amsterdam streets dangerous

A Swedish app maker, an American actress and a Brazilian DJ, out to make the Amsterdam streets dangerous

Next day I went to visit the Appsterdam community at A-lab, just north across the river from the central station. After a lunchtime presentation about Sencha Touch, I had a coffee with the very friendly Patrik Beeker who also showed me around in the area. Working a bit in the awesome co-working space and walking around in the city, I got a great feeling about Amsterdam. Too bad I was due to leave just the day after. Reflecting on it now, I'm definitely going back to stay longer.

Peeps working at Appsterdam HQ

Peeps working at Appsterdam HQ

My stay in Amsterdam concluded by the mandatory Flying Pig pub crawl. Only problem was that my flight to Valencia was due for lift-off at seven in the morning, so I figured the best idea was to keep on going all night and then head straight for the airport, which I did...

Valencian football and decay

Last week I stayed in Valencia and frankly, got no work done whatsoever. Five minutes after walking inside of the Purple Nest Hostel, I met a german guy who told me about a football match the same night. I've never been that much interested in watching football, but now when I was in Spain, I couldn't miss the opportunity. I knew that a week later, the Copa del Rey final between Barcelona and Real Madrid was going to be played in the Valencia stadium, but the ticket prices were outrageous. This match however, was between the local Valencia CF and the Swiss team FC Basel in the Euroleague with tickets for a measly 15€.

First live football match of my life, Mestalla stadium, Valencia

First live football match of my life, Mestalla stadium, Valencia

The match was a rematch from which Valencia came out losing 3-0. Naturally, the crowd wasn't too excited as the referee blew the starting whistle. But then Valencia started to absolutely dominate the match, scoring more and more and leading with 3-0 as the regular match time ran out. Since I never knew about the previous match, I almost left while everybody else waited for overtime. In the end, Valencia won with a whooping 5-0 and the crowd went absolutely crazy, hugging strangers and chanting songs while exiting the arena to party on the street.

Getting to Alicante

After partying for five nights straight, me, a Belgian teacher and a kid from Finland went to stay two nights in Alicante just two hours down the coast from Valencia. Getting there was another story though. After having no luck finding ride sharing through BlaBlaCar, we checked the train schedule and decided to go by train instead.

Arriving at the station we thought the train left from, turns out it was the wrong one. Now we had to take the slowest shuttle bus ever to the other station just a few hundred metres away. With only 10 minutes left until departure, we ran towards the ticket vending machines when the next problem turned up: we couldn't pick Alicante among the destinations in the machine. By some odd reason, these particular tickets could only be bought in the manual ticket booth. After trying to convince some grumpy old people to let us pass to the front of the queue, things were starting to get desperate.

It was 5 minutes until departure. We already knew we couldn't go through the glass gates to board the trains without a ticket, but with desperation kicking in we ran towards the gate guard, begging him to let us through anyway and buy a ticket on the train. While he refused us and turned around, the Finnish guy found a leftover ticket on the ground and it actually still worked! One by one, he stood on the other side, opening the gates for the rest of us while the guard stood literally two metres away from us. Running towards the platform, we made it with seconds to spare!

Tense moments on the train to Alicante

Tense moments on the train to Alicante

While we made it to the actual train, we now had to face the problem of the ticket inspector. Since we were pretty sure we couln't buy tickets on the train, we figured our best bet was to pretend to be asleep so the he wouldn't want to disturb us. After some tense 30 minutes, we spotted the inspector walking down the wagon. Taking a quick look at one another, me and the finnish guy entered sleeping position. Suddenly, I heard the Belgian guy Simon (who taught Spanish) starting to talk with the inspector. I knew Spanish good enough to follow the conversation but not to explain the situation as well as Simon did. Finally, the ticket inspector let us stay on the train and get tickets. Although we paid 10€ more than we originally planned, this was considerably cheaper than taking a fine.

Tabarca, the smallest permanently inhabited island in Spain

Tabarca, the smallest permanently inhabited island in Spain

As we arrived in Alicante I immediately fell in love with the place. The atmosphere was much calmer than in Valencia or London and the beaches were stunning. Still, there was a lot of young people from the University and the night life more than adequate. Thus, I decided to stay here while the others went on to other places, something I know I will not regret.

Magnificent view from the Santa Barbara castle walls in Alicante

Magnificent view from the Santa Barbara castle walls in Alicante

UK Mobile Data for 2£ per GB

Quick tip for digital nomads heading to the UK:

If you want cheap mobile data, get a 3 (Three) Pay As You Go Mobile Broadband SIM. For a measly 2£ at the nearest WH Smith (there's a lot of them on airports), you get a starting allowance of 1GB data. As soon as that runs out, just buy another SIM and repeat!

I did this once a week during my three week stay in the UK and except for the London subway, the network ran flawlessly!

The SIM-card works as well on your smart phone as on a tablet or MiFi device. Also, since it's meant for mobile broadband, tethering is allowed unlike with many other pay as you go SIMs.

3mobileBroadband.jpg

Subscribe for more traveling tips for digital nomads!

Of Things To Come

I have a confession to make.

Three weeks into my trip, I am starting to realize how naive I have been. In my wild fantasies before traveling was reality, I imagined all the near-infinite time I would have on my hand. Compared to doing 8 hours of work on the same project day in and day out, I was surely going to be ten times more productive in a new environment. Boy I was wrong.

It's probably just a matter of getting accustomed to being on the road, but the latest weeks have been devastating in terms of actually getting things done. I've been finding myself spending more time on finding the best & cheapest hostel, flight route, grocery store, gym, supplements store and so on than actually being productive with writing & coding.

With all that said, I'm slowly finding calm in midst of chaos. Saying no to going out and being able to think straight in a chatty room is still hard, but it's progressively getting easier.

Here's an update of what I'm actually working on and topics for upcoming posts

First of all, I've been working for a while on this lengthy blog post about pagination. This one I should have written already back in 2012 when I implemented the fluent pagination technique I'm proposing, in the ICA Handla app. The post will feature thoughts from a UX perspective and how that affects UI in apps, together with client and server considerations and some iOS sample code.

There will also be posts with travel tips for digital nomads and lifters, including how to get cheap mobile data, gym passes, nutritional supplements and places to work, among others.

The thing that still worries the hell out of me is my economic situation. I really need to do marketing for my currently only paid app Min Firma, but that's a topic for another post. First, I need to become productive.

I'm staying here in London until tuesday and then I'm gonna visit the Appsterdam peeps. Really looking forward to that even though I'm just staying for two days. Then it's off to Spain where I'll probably be traveling around for a month or so, trying to achieve that programmer dream of sitting with your laptop on the beach and watching the sunset with a drink in your hand. Those will be good times :)

CC Image courtesy of Librarian by epsos .de on Flickr  

CC Image courtesy of Librarian by epsos .de on Flickr