Mikael Hultgren  —   I'm a coder, baker and climber, by day I code for a living and by night I develop apps for the  landscape of devices. You can find me as “blomma” on Twitter and Github.

SLIDE IN UIVIEWCONTROLLER

On the road to iOS 7 I’ve decided to rethink some of the design decisions of Stray and this is the journey.

After having watched some of the WWDC 2013 sessions on the new transition animation frameworks for presenting a controller I decided this was something I wanted to do, more specifically to have my settings controller slide in from the left on a swipe/pan gesture from the left edge.

This is what I ended up with

Settings dialog

First problem I tackled was the swipe/pan gesture. Since Stray is using a UIPageViewController I simply added a transparent UIView that covered the left side and attached my gesture to that. This won’t register a swipe/pan on the actual settings dialog but for the moment that is good enough.

Next up was the transition animation controller, i recommend having a look at session 218 for a thorough review of what it can do, but in essence what you do are the following steps.

[self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"PreferencesViewController"]
                           animated:YES
                         completion:nil];

And in the controller being presented, here PreferencesViewController, you have to set up the following to make sure that it uses your custom transition animation.

self.modalPresentationStyle = UIModalPresentationCustom;
self.transitioningDelegate = self;

UIModalPresentationCustom is the new modal presentation style in iOS 7

On the presented controller you make it conform to the UIViewControllerTransitioningDelegate protocol, in this case PreferencesViewController and you implemented these two methods

- (id <UIViewControllerAnimatedTransitioning> )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
	return [[PreferenceAnimationController alloc] init];
}

- (id <UIViewControllerAnimatedTransitioning> )animationControllerForDismissedController:(UIViewController *)dismissed {
	PreferenceAnimationController *controller = [[PreferenceAnimationController alloc] init];
	controller.isDismissed = YES;

	return controller;
}

As you might have guessed, PreferenceAnimationController is were the magic happens. It’s a class that conforms to the UIViewControllerAnimatedTransitioning protocol.

For a non interactive animation (oh yes, there are interactive one’s to) you implement these two methods

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    UIView *inView = [transitionContext containerView];
    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
    UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;

    if (self.isDismissed) {
        UIView *preferenceOverlay = [inView viewWithTag:1];

        [UIView animateWithDuration:0.4
                              delay:0 options:UIViewAnimationOptionCurveEaseOut
                         animations:^{
            fromView.center = CGPointMake(-CGRectGetMidX(inView.frame), CGRectGetMidY(inView.frame));
            preferenceOverlay.alpha = 0;
        } completion:^(BOOL finished) {
            [preferenceOverlay removeFromSuperview];
            [fromView removeFromSuperview];
            [transitionContext completeTransition:YES];
        }];
    } else {
        UIView *preferenceOverlay = [self createOverlayViewWithFrame:inView.frame];
        [inView addSubview:preferenceOverlay];

        toView.center = CGPointMake(-CGRectGetMidX(inView.frame), CGRectGetMidY(inView.frame));
        [inView addSubview:toView];

        [UIView animateWithDuration:1
                              delay:0
             usingSpringWithDamping:0.7f
              initialSpringVelocity:2.3f
                            options:UIViewAnimationOptionCurveEaseOut
                         animations:^{
                             preferenceOverlay.alpha = 0.8;
                             toView.center = CGPointMake(toView.center.x + 200, CGRectGetMidY(inView.frame));
                         } completion:^(BOOL finished) {
                             [transitionContext completeTransition:YES];
                         }];
    }
}

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 1;
}

They are responsible for moving the presented controllers view into the correct position when presented and when dismissed. You can of course have two different animation controllers, on for presenting and one for dismissing, but it seemed simpler with just the one.

And that is that, honestly, it was easier than the WWDC 218 session made it look like, but then of course, if you want harder then i suggest you try your hand at the interactive kind.

MEDIS LUNACY

128 days ago, or so, I returned my Urbanears Medis headphones, for the fourth time they developed a glitch which drops the sound in a random ear and for the fourth time I had the pleasure of dealing with their customer support.

Curious as I am it got me wondering when I could expect my current number 5 to break down. So I dug into my gmail folder to unearth the dozen or so messages that had been flying back and forth between their customer support and me to construct a timeline of sorts.

Shipped Return Days in service
2011-06-13 2011-09-07 86
2011-09-21 2011-12-23 93
2011-12-29 2012-05-02 125
2012-05-23 2012-11-12 173
2012-11-21    

As we can see, the time my buds stay in service is increasing, but not because of better build quality, no sire bob. It has more to do with the fact that I am being super careful these days on how I use them, how I fold them and how I carry them to the point of being anal.

I bought them for 500 SEK and at this point I’m only in this to see how many times they will exchange them for me. With a bit of luck (depending on how you see this) these might be the last headphones I will ever have to buy.

COMPATIBLE

Work has been in the key of RoR like migrations, first with Entity Framework and then with fluentmigrator, it really beats the pants of trying to keep everything in sync manually. So being inspired by that i thought i give my own code for Stray a good unpack and unhack in the segment of code i use for migration of old data.

Altho i haven’t made all that much in sweeping changes to the data structure there is a few smaller things that has accrued over time and my adhoc migration code is starting to feel the weight of it, but more importantly it was getting harder and harder to actually discern what was going on since the migration code was mixed in with the code that read it.

In Stray i have two things, two structures to account for, one is UserDefaults and the other is our dear old friend CoreData. It would not be the end of the world if i lost track of UserDefaults but why settle and as for CoreData, that would be less good. So for UserDefaults i created a stateCompatibilityLevel key which stores the last migration done and for CoreData a Entity called Compatibility was created for the same reason. In the Info.plist i also created a StrayCompatibilityLevel which is used for holding the current level of migration.

With that done i moved on to the code, i added a singleton called CompatibilityMigration which gets called in didFinishLaunchingWithOptions.

and in that wonderful singleton this little gem resides

- (void)migrateToCompatibilityLevel:(NSNumber *)toLevel lastCompatibilityLevel:(NSNumber *)lastLevel migrationBlock:(void (^)())migrationBlock {
	// toLevel > lastLevel && toLevel <= appCompatibilityLevel
    NSNumber *appCompatibilityLevel = [[[NSBundle mainBundle] infoDictionary] objectForKey:STRAY_COMPATIBILITY_LEVEL_KEY];

    if ([toLevel compare:lastLevel] == NSOrderedDescending && [toLevel compare:appCompatibilityLevel] != NSOrderedDescending) {
        migrationBlock();
    }
}

which i borrowed heavily from MTMigration, the idea being that you call it with a block of code that will perform the migration ToVersion and at the same time making sure that we don’t reapply the same migration twice. I tweaked it slightly to take lastVersion as an argument since i have more than one data store of sorts that i want to migrate.

The actual call to it looks like this

[self migrateToCompatibilityLevel:@1 lastCompatibilityLevel:[self coredataCompatibilityLevel] migrationBlock:^{
    NSArray *events = [Event MR_findAll];
    [events enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [obj setGuid:[[NSProcessInfo processInfo] globallyUniqueString]];
    }];

    NSArray *tags = [Tag MR_findAll];
    [tags enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [obj setGuid:[[NSProcessInfo processInfo] globallyUniqueString]];
    }];

    [self setCoredataCompatibilityLevel:@1];
}];

The last line

[self setCoredataCompatibilityLevel:@1];

is there to bump up the CompatibilityLevel after it is done. Also, i opted to make the levels NSNumbers instead of… oh NSStrings, no real reason except for the fact that it was the first things that occurred to me. Notice also the wonderful world of literals as in @1, really takes away some of the dreary typing.

All in all, a fairly nice solution since all i have to do the next time i bump StrayCompatibilityLevel is to make a new migrationblock .

PATH OF MOST RESISTANCE

For the last oh say half hour I’ve been struggling with this boulder problem, I’ve seen it done one time with a technique that my center of gravity (to tall) is unkind to. So banging my body against this problem, seemingly getting nowhere and wondering what I am doing wrong. And the simple answer is… nothing, absolutely nothing, I’m simply learning. With each attempt I’m getting a bit more strong (and more tired, which is why I’m typing this sipping on a cup of earl green) and learning to adjust my body a bit better. But more importantly I’m building up a mental picture of me clearing the problem, it doesn’t feel like a problem that can’t be solved, more like a problem that has a solution that I just can’t see yet.

And the fact is I may never solve it, they might remodel the problem away by the time I come here next or I may simply suck to much, who knows. But that doesn’t matter, because as we all know, the path is way more interesting than the goal and the most interesting living you can have is to be on the path as much as possible.

So here I am, sipping my Earl Green, waiting for my muscles to chillax a bit and watching the rest of the climbers do their thing.

LOST TIME

Why isn’t travel time to/from work counted as work? As soon as you leave for work you are committed, it’s no longer your time. We fill it with reading, listening to music and a fair number of us spend it preparing for work, thinking about what we should do, how we should do it. From an employers standpoint it seems cut and dry, but imagine if you were offered a job that included the travel time in the working hours. Think about how much time you spend each day just getting to and from work, how much would it be worth to you to have that time back? A lot I’m willing to bet. And yes, I see the potential for abuse here, but then again, why would you hire someone to work for you if you didn’t trust them.

It seems weird to me, employers try to attract you with salaries, interesting jobs and whathaveyounot perks, but time seldom seems to be on the table of offers and yet that is the resource we have the least of.

Or maybe it is just me…