Johan Sørensen2013-03-06T23:57:52+01:00http://johansorensen.com/Johan Sørensenjohan@johansorensen.comTiny bits and pieces on ReactiveCocoa2013-03-05T23:57:00+01:00tag:johansorensen.com,2013-03-05:1362524220
<p>When <a href="https://github.com/ReactiveCocoa/ReactiveCocoa">ReactiveCocoa</a> was released last year I gave it a quick look, but it didn’t really click with me, which made me feel dumb so I made the usual mistake of dismissing it.<br />
(<em>sidenote: in a previous life I learnt the hard way to not dismiss the work of Github, but that’s an entirely different story and circumstances. Old dog. No new tricks</em>).</p>
<p>A few weeks ago I decided to give it a proper look and integrate it into <a href="http://frosthaus.com/sequence/">Sequence</a>. This post isn’t meant as a tutorial or even an introduction, it’s just a collection of things that weren’t immediately obvious to me. I suggest reading <a href="http://blog.maybeapps.com/post/42894317939/input-and-output">this</a> and <a href="http://nshipster.com/reactivecocoa/">this</a>, as well as the <a href="https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Documentation">official documentation</a>, which does a better job than me at introducing the high-level concepts.</p>
<h4>Use <code>RAC()</code> and <code>RACAble()</code> to easily bridge between worlds</h4>
<p><code>RAC()</code> lets you assign a property to a signal, meaning that whenever the signal sends its next event it’ll set the property to the value of that signal.</p>
<pre><code>
// @property (nonatomic, assign) BOOL edited;
RAC(self.edited) = [textChanged map:^id(NSString *txt){
return @([text length] > 0);
}];
</code></pre>
<p><code>RACAble()</code> as well as <code>RACAbleWithStart()</code> creates a signal which acts as an observer for the given key path. RACAbleWithStart simply starts with the current value as well. I’ve been using this a lot instead of the traditional cocoa KVO:</p>
<pre><code>
// self.edited will be true whenever self.text is changed to be non-empty
RAC(self.edited) = [RACAble(self.text) map:^id(NSString *txt){
return @([text length] > 0);
}];
</code></pre>
<p>Further bridging between the reactive and the non-reactive worlds can be done with <code>rac_liftSelector:withObject:</code>, such as setting the state of a NSButton based on a signal:</p>
<pre><code>
RACSignal *inspectorVisible = [RACAbleWithStart(self.timelineViewController.isTrackInspectorVisible) map:^id(NSNumber *visible) {
return @([visible boolValue] ? NSOnState : NSOffState);
}];
[self.toggleTrackInspectorButton rac_liftSelector:@selector(setState:) withObjects:inspectorVisible];
</code></pre>
<p>Of course, the real power of ReactiveCocoa is that signals are composable, looking at our <code>textChanged</code> signal again we can throttle the rate at which we receive the signal, for instance to avoid unnecessary network requests (think autocompletion of remote values) or keeping UI updates reasonable</p>
<pre><code>
// only update self.edited if the textChanged signal hasn't sent any events in the last 0.3 seconds
RAC(self.edited) = [[textChanged throttle:0.3] map:^id(NSString *txt){
return @([text length] > 0);
}];
</code></pre>
<h4>Use RACSubject for bridging between worlds in a controlled manner</h4>
<p>RACSubject is a signal which you can manually control. This is extremely useful when bridging between worlds and you want to control when the signal is sent (as opposed to just when a property changes). For instance in <a href="http://frosthaus.com/sequence/">Sequence</a> I manually send a signal through a RACSubject whenever the marker is moved in the timeline. My timeline class simply exposes the <code>RACSubject</code> as a <code>RACSignal</code> externally:</p>
<pre><code>
// JKSTimelineView.h
@interface JKSTimelineView : NSView
@property (nonatomic, strong, readonly) RACSignal *frameChanged;
// ...
@end
// JKSTimelineView.m
@interface JKSTimelineView ()
@property (nonatomic, strong, readonly) RACSubject *frameSubject;
@end
@implementation JKSTimelineView
- (RACSignal *)frameChanged
{
return [[self.frameSubject distinctUntilChanged] startWith:@(0)];
}
#pragma mark - Private methods
- (void)moveMarkerToFrame:(NSInteger)frameIndex
{
// ...
[self.frameSubject sendNext:@([self currentFrameIndex])];
}
@end
</code></pre>
<p>I return the internal <code>frameSubject</code> from the <code>frameChanged</code> property, thereby hiding the implementation detail of the fact that I use a RACSubject internally. Furthermore, <code>frameChanged</code> starts by sending 0 and only sends events if the value of the signal actually changes.</p>
<p>A useful subclass of RACSubject is RACReplaySubject, which stores any values sent to it and replays it to new subscribers. This is very useful for network requests or other heavy work you only need to do once.</p>
<h4>Using it</h4>
<p>It took a while for me to get ReactiveCocoa and conceptually it’s very different to the style of imperative programming I usually do. But even though the abstractions seem high I certainly think it’s been worth it integrating it into Sequence.</p>
<p>I admit I don’t currently harness the full power of ReactiveCocoa, as I don’t do a lot of signal composing. I’ve been meaning to refactor parts of the rendering pipeline in Sequence to use ReactiveCocoa, as it really could benefit of a cleaner abstracting of the inter-dependent steps it needs to run through in order to analyze and apply the deflickering to the photos. But I’m starting slow, replacing the UI related parts that previously relied on bindings and KVO to use signals instead and overall I think it comes out nicer and easier to understand and work with, provided you know how ReactiveCocoa actually works of course.</p>
<p>As always, the best way to learn something is to just jump in and use it in a real project.</p>
Introducing Sequence, the Mac timelapse editor2012-12-20T08:00:00+01:00tag:johansorensen.com,2012-12-20:1355986800
<p>After many months of hard work I released my latest project last week, a Mac app that I’m incredibly proud and excited about.</p>
<p><a href="http://frosthaus.com/sequence">Sequence</a> is a timelapse editor, meaning it’ll take the hundreds or thousands of photos you’ve patiently created with an intervalometer and assemble them to a movie playing back at, say, 25 frames per second. It also includes a nifty deflickering engine that will remove most, if not all, aperture or shutter induced flicker between your photos.</p>
<center><img src="http://johansorensen.com/files/sequence_icon_512x512.png" title="Sequence 1.0" alt="Sequence 1.0" /></center>
<p>As the review times for the Mac App Store are currently <a href="http://reviewtimes.shinydevelopment.com/mac-annual-trend-graph.html">ridiculous</a> (I waited 21 days for 1.0), I’ve already submitted 1.1 and I am hard at work on the next big features which are going to be fantastic.</p>
<p>I can’t wait to show you the things that are coming for <a href="http://frosthaus.com/sequence">Sequence</a> in the next year!</p>
Back to the Mac2012-10-11T23:45:00+02:00tag:johansorensen.com,2012-10-11:1349991900
<p>When I left my previous company I spent some time thinking about what I wanted to work on next and I kept coming back to one thing: the Mac. Tablets and other powerful mobile devices are here to stay, and it’s software for those I’ve worked on for the past few years, but whenever I wanted to get serious work done I’ve always gone back to a laptop or desktop.</p>
<p>Casual computer use (if there ever was such as thing) has certainly changed to being mostly on mobile devices, even for a computer dork such as myself. But most of the software I use and admire everyday are running on the Mac. So that’s what I’ve decided to work on again.</p>
<p>For the past few months I’ve been working on a wonderful new Mac app and I’ve founded an <a href="http://frosthaus.com">independent company</a> to go along with it and any future applications. While the app is nearing beta quality, it’s still a bit too soon to talk about it but I’ve really enjoyed working on a Mac app again. I’ve learnt a lot since I cobbled together my <a href="http://johansorensen.com/paparazzi/">first Mac app</a> eight years ago and it feels incredibly good to be able to funnel that into a product I actually believe in again.</p>
<p>As the saying goes, stay tuned.</p>
Modern modern Objective-C2012-08-28T22:11:00+02:00tag:johansorensen.com,2012-08-28:1346184660
<p>With the arrival of LLVM and the modern runtime Objective-C has been having some steady progress and while it’s not really moving so fast that it’s hard to stay up to date, some habits may be hard to change.</p>
<p>Below is a far from complete list of things that I look out for on the syntax-level that I consider to be modern Objective-C. You, the seasoned cocoa developer, know all of these and probably agree with most of them, you might even doze off reading it as they’re so incredibly obvious. I’ve left out some of the most obvious ones, such as ARC and blocks.</p>
<h3>Synthesized-by-default properties</h3>
<p>Available with the LLVM Compiler 4.0 there’s no need for <code>@synthesize</code> in the implementation when declaring properties. Note that some property declarations together with your own getters or setters may require you to add a synthesized instance variable, readonly properties with your own getter method for instance.</p>
<h3>Instance variables prefixed with underscore</h3>
<p>The default auto-synthesis of properties creates instance variables prefixed with an underscore. Seconded only by dot-notation this has been one of most debated tiny issues in the community. I don’t think we’ll ever get a more clear answer that prefixing instance variables with an underscore is perfectly fine.</p>
<h3>Instance variables in the implementation</h3>
<p>Internal instance variables should be declared at the start of the <code>@implementation</code> block. This will Keep the internal instance variables out of your public headers, even if you and your colleagues the only one seeing them. If you really feel that some instance variables should be part of your public API, then consider exposing them using properties instead.</p>
<h3>Header files represent the clean public API</h3>
<p>The header file should only represent properties and methods which you consider part of your objects public API, this makes it easier for everyone else to see how the object is supposed to be used without being distracted by your internal object logic. With instance variables in the implementation file, there’s no point in exposing them to the world. If you need to declare internal “inter object” methods, use a separate header file (MyClass+Private.h for instance) that your subclasses or fellow objects can import.</p>
<p>However, one thing I struggle a bit with to fit into this regime is IBOutlets. Not the <code>delegate</code>, <code>dataSource</code> or other outlets I really want to be public API, but your average textfield connected to a view controller. These are usually very much internal API, yet I still hook them up to properties in my header file rather than in a private class extension in the implementation file. I suspect the biggest reason is that Xcode’s assistant defaults to the corresponding header file when editing a nib file, but it also makes it easier for me to see if all outlets are hooked up and what they’re named.</p>
<h3>Number, array and dictionary literals</h3>
<p>Should be a no-brainer if your deployment target supports it. Less typing, less repetition. Apart from the <code>@</code> avalanche.</p>
<h3>NS_ENUM</h3>
<p>I’m still warming up to this one as it’s new in the 10.8 and [redacted] SDKs. You’d declare your enumeration constants like this:</p>
<pre><code>
typedef NS_ENUM(NSUInteger, MyClassSetting) {
MyClassSettingAwesome,
MyClassSettingDull
};
</code></pre>
<p>The upside is you’ll get additional warnings (with <code>-Wconversion</code>) if you pass something not in the enum to a type, but even more useful (depending on your viewpoint) is that Xcode’s autocomplete will will be scope to the values of the enum. For more information on these fixed underlying type enums (which is what the macro expands to) see <a href="http://weblog.bignerdranch.com/829-enum-num-num/">Mark Dalrymples post</a> on the subject of enums.</p>
<h3>“Private” methods without a private class extension interface</h3>
<p>With LLVM you don’t need to define private/internal methods in a private class extension interface as LLVM’s two-pass compiler will figure it out, regardless of whether those “undeclared” methods are implemented before or after the location in the implementation file where you’re calling them.</p>
<h3>Related result types for initializers</h3>
<p>LLVM introduces the <a href="http://clang.llvm.org/docs/LanguageExtensions.html#objc_instancetype"><code>instancetype</code></a> type, which you’d typically use as the return type of your initializers rather than <code>id</code>. In return you can additional checking that your initializer actually return what you’d expect (nil or an instance of your class’ static type).</p>
<p>Apple has a <a href="https://developer.apple.com/library/mac/#releasenotes/ObjectiveC/ObjCAvailabilityIndex/_index.html#//apple_ref/doc/uid/TP40012243">availability index</a> on some of these features.</p>
Pausing and controlling the speed of Core Animation2012-03-24T10:47:00+01:00tag:johansorensen.com,2012-03-24:1332582420
<p>Core Animation is one of the finer frameworks in the SDK, it abstracts away a lot pesky details in a declaritive way. What declaritive means here is that you tell a CALayer to, for instance, go somewhere by changing its <code>position</code> property and it’ll animate it’s way there. You can also build up your own animations with the varius CAAnimation subclasses, such as CABasicAnimation and CAKeyframeAnimation. With these animation classes you set various properties (to/from values, keyframe values) along with things such as the length and the timing function, but I became curious on how one would stop an animation “mid-flight” or change the speed once it has started. Turns out it’s fairly easy.</p>
<h3>Stopping and resuming animations</h3>
<p>In Core Animation, time is modelled in a tree-like fashion, with the speed of one animation being relative to the speed of its parent animation. What that means that if you set a child animation to take 10 seconds it’ll still take 10 seconds, however, if you change the speed of the parent animation any child animations will will change its speed relative to that. Both CAAnimation and CALayer’s themselves adopt the <a href="http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CAMediaTiming_protocol/Introduction/Introduction.html#//apple_ref/occ/intf/CAMediaTiming">CAMediaTiming</a> protocol which models this time space relationship.</p>
<p>Like Apple describes in this <a href="https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html">Technote</a>, in order to pause an animation you set the speed to 0.0 and its timeoffset to the current time (relative to its parent time). It may seem counter intuitive that just settings the speed to zero isn’t enough, but if you only did that then it would simply return to its initial starting point as we don’t freeze the time and the <code>speed</code> property simply maps the child animations time space from its parent time space.</p>
<pre><code>layer.speed = 0.0;
layer.timeOffset = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
</code></pre>
<p>Starting it involves setting the speed to 1.0, but also finding the current <code>timeOffset</code> (where we paused it), resetting it, and setting the <code>beginTime</code> based on the time difference between now and when it was paused so the animation will start exactly from where it was paused.</p>
<pre><code>layer.speed = 1.0;
CFTimeInterval pausedTime = layer.timeOffset;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
</code></pre>
<h3>Slowing down time</h3>
<p>Slowing down time or speeding it up becomes easy once we learn how Core Animation maps time spaces:</p>
<pre><code>layer.timeOffset = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.beginTime = CACurrentMediaTime();
layer.speed = 0.5;
</code></pre>
<p>Chaning the speed of an animation means we have to change the speed obviously, but we also have to remap the time space into the current time. So we set the timeOffset to the layer’s current time and the beginTime to our global time.</p>
<p>I haven’t actually had a use for this in a real app, as it was one of those “I wonder how…” moments, but it might be useful for someone out there.</p>
Headphones2012-01-12T19:12:02+01:00tag:johansorensen.com,2012-01-12:1326391922
<p>If you spend any amount of time in front your computer with headphones, you should invest in a decent pair. Just as you should invest in a decent desk and chair. I’ve been through a few headphones, from cheap and crap to expensive and wonderful and even expensive and crap.</p>
<p>Listening to music is very much an individual taste whether it’s genre or how the music actually sounds through a pair of speakers or headphones. Audiophiles will spend the gross national product of a small country (and half of that on cables) to try and figure it out, but even if you’ll only invest a smaller amount (say $170-$600), you’ll hear a difference when it comes to headphones and it’s definitely something I recommend investing in if you enjoy listening to music all day long. Even a specialized headphone-amplifier can lift some dull-sounding headphones to new heights.</p>
<h3>Requirements</h3>
<p>I have the following three requirements for a pair of headphones:</p>
<h3>Comfort</h3>
<p>They must be comfortable. No point in having a pair of expensive headphones if you can’t stand to wear them. In a perfect world you should forget you’re wearing them completely.</p>
<p>Things that may affect comfort are weight and how tight they clamp on your head and ears. Personally I only want to use full-size (circumaural) headphones, it’s the only kind of headphone that I can easily forget is there, as over- or in-ear headphones have a tendency to physically remind me that they’re there all the time either by squeezing on my ear or itching.</p>
<h3>Sound quality</h3>
<p>Sound quality must be enjoyable. They don’t have to measure perfectly on some random audiophile scale, but the sound must not wear me out or sound artificially enhanced. Those two usually go hand in hand, as headphones that enhance for instance the bass will over time wear my ears out due to being pumped full of bass all day long.</p>
<p>I find that I prefer headphones that seem light on bass when you try them in the store compared to other offerings, but when you get home and listen to them for extended periods my ears don’t get as worn out as with a pair of bass-heavy ones. The same goes for the upper tones, constantly having those high tones tickling your ears gets weary as well. So fairly neutral is what I’m going for in a pair of headphones.</p>
<p>If you troll any random audiophile forum you’ll read much about some hunt for the magically neutral sound, but with a wide soundstage that makes you feel like you where transported to the same room where the recording took place. Which is of course like hunting for a unicorn, the sound is already mutated by the microphone(s’) placement and how the sound engineer mixed the sound. The kind of neutral I’m hunting for is the kind that makes my ears feel comfortable and the music sound <em>enjoyable</em>, according to my own mental impression and scale.</p>
<h3>No unwanted noise leaking</h3>
<p>This one is optional. But not if you use them in a public place, whether that’s on the subway or in an office. Doesn’t matter if everyone share your taste in music, no-one wants to hear your muffled noise. At the office I only use closed headphones, in my home office I got a pair of open ones as I’m not likely to disturb any else in the same room.</p>
<p>There’s a tradeoff here between open and closed headphones, as open headphones <em>generally</em> tend to sound better, since it’s easier to control the sound as it doesn’t bounce around next to your ear so much.</p>
<p>Remember, not everyone listens to music when they work and they’ll hate you if they can hear you listening to something through your open headphones.</p>
<h3>Ohms & Amplifiers</h3>
<p>Generally headphones are rated, among other things, in impedance as a measure of how much power is required to drive them. As a rule of thumb, if your headphones have an impedance above around 32 Ohms your average computer, laptop or portable music player isn’t going to be able to drive them reasonably and you need an amplifier in-between.<br />
Luckily the market for headphone specific amplifiers are equally vast (and confusing) as the actual headphones themselves and you can easily break your budget on these things as well. Not only that, but they also affect the sound a bit, making it sound “warmer” or “colder” or with less or more bass and so forth. But unless you’re a total audiophile the difference is negligible, but certainly something to pay attention to as it’s noticeable.</p>
<h3>My headphones</h3>
<p>I’ve been through a lot of headphones. Let’s take a look at some of the recent ones, there’s both good and terrible purchases here. I’m not going to do a full review here as frankly I’m not authoritative enough on this, but these are a few of my choices from the past year:</p>
<h3>AKG K-271 MKII + Maverick Audio TubeMagic D1</h3>
<p>My current office setup at work. While the <a href="http://www.headphone.com/headphones/akg-k-271-mk-ii.php">K 271</a> will do fine without an amp, they really shine when they receive a bit of power. The <a href="http://www.mav-audio.com/base/product/tube_magic_d1">TubeMagic D1</a> really makes them open up a bit more than straight from the iMac’s audio jack. The D1 is a tube amplifier but sadly you don’t get to hear any of those smooth tubes when using the headphone jack, only through the RCA analog out. Still the D1 is a dirt cheap DAC with decent quality and enough power to drive most headphones.</p>
<h3>Monster Beats Pro</h3>
<p><a href="http://beatsbydre.com/products/Products.aspx?pid=B5616&cat=1">This</a> was one of those regrettable spontaneous purchases. They fooled me completely, bought when I was at WWDC in San Francisco and sold when I got back to Norway. They enhance bass. They enhance bass <em>a lot</em>. They enhance bass a <em>metric ton</em>.</p>
<p>They <del>impressed</del> fooled me at the noisy store but when worn for any length of time the enhanced bass just got annoying and wore me out, in addition to the weight. All that aluminum really makes them heavy.</p>
<p>I really regretted buying ever buying these, probably because I mostly felt fooled when I got home and compared them to me existing headphones, so they got sold again quickly. One of my coworkers swear by them though, so each to their own.</p>
<h3>Hifiman HE-300 + Nuforce Icon HDP</h3>
<p>My home office setup. The <a href="http://www.head-direct.com/Products/?act=detail&id=108">HE-300</a> is currently my favorite pair of headphones. They’re open headphones so sadly I can’t use them at work, but I just love sitting and listening to these at home. They’re rated at 32 ohms so they can be driven by an iPhone or laptop, but like the K271 they really open up when given some power from an amp. The <a href="http://www.nuforce.com/hp/products/iconhdp/index.php">Nuforce HDP</a> gives plenty of power to these and has a very good USB DAC (better than the TubeMagic D1).</p>
<p><img src="/images/hifiman-he-300.jpg" alt="" /></p>
<h3>Parting words</h3>
<p>Must of the music I listen to is of the instrumental kind, various kind of classic and heavy rock all the way up to metal, so the choices above mostly reflect this and as you’ve already guessed I’m not a fan of artificially enhanced bass as it easily drowns out other details on headphones in this price range.</p>
<p>Like most gadgets, spending a lot of money upgrading headphone gear is easy once you get into it, but sooner or later you’ll find a sweet spot of price versus performance for your taste where upgrading doesn’t make any sense. While I’ve occasionally contemplated toying with other amplifier combinations I’m currently satisfied enough with my HE-300 setup at home and the K-271 at work.</p>
<p>You should buy some decent headphones. Try a few different ones first as there’s quite a difference and it is very much a matter of taste, but the reviews don’t always reflect that.</p>
A scrollview in a tableview cell, properly2011-12-21T08:58:02+01:00tag:johansorensen.com,2011-12-21:1324454282
<p>Looking at what search terms people use to find this site I see that my article on <a href="/articles/touches-and-uiscrollview-inside-a-uitableview.html">embedding a UIScrollView inside a UITableViewCell</a> is among the ones that gets the most hits. Which is a shame since that approach is a <em>terrible way</em> of solving it these days.</p>
<p>After iOS 2.1 (or iPhoneOS as it was called back then) or thereabout that terrible responder chain hack is no longer needed, all you need to do is add the scrollview as a subview of the cell and you’re good to go:</p>
<pre><code>
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50.0f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"ScrollCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
UIScrollView *scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 25, 320, 25)];
scroller.showsHorizontalScrollIndicator = NO;
UILabel *contentLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320*4, 25)];
contentLabel.backgroundColor = [UIColor blackColor];
contentLabel.textColor = [UIColor whiteColor];
NSMutableString *str = [[NSMutableString alloc] init];
for (NSUInteger i = 0; i < 100; i++) { [str appendFormat:@"%i ", i]; }
contentLabel.text = str;
[scroller addSubview:contentLabel];
scroller.contentSize = contentLabel.frame.size;
[cell addSubview:scroller];
}
cell.textLabel.text = [NSString stringWithFormat:@"cell #%i", indexPath.row];
return cell;
}
</code></pre>
<p>Nothing fancy going on here, just creating a scrollview, add a wider subview, set the contentSize of the scrollview and add it to the UITableViewCells view hierarchy.<br />
I usually prefer to use a UITableView subclass instead of setting up cell view hierarchies in the view controller, but you wouldn’t just copy-paste example code without thinking now would you?</p>
Nibs vs. code, a journeyman's perspective (or lack thereof)2011-08-06T01:18:02+02:00tag:johansorensen.com,2011-08-06:1312586282
<p>Like all languages and frameworks, Cocoa has some topics that will polarize its programmers. This one isn’t about dot-notation, for once, but laying out user interface in nibs/xibs (<a href="http://en.wikipedia.org/wiki/Interface_Builder">interface builder</a> files) versus building it in plain old Objective-C code. With nibs (even though their modern extension is .xib) you layout the user interface ín a graphical UI and connect it with your code. With code you allocate, initialize and place the UI element(s) entirely in code, most likely within your controller.</p>
<p>First things first, at its core programming is a discipline of <em>balance</em>. Balance with performance and implementation time. Balance between readability and understandability. Balance between implementation time and understanding it in one, two, twelve months from now ad infinium.<br />
Some times it’s faster to add some UI elements in code, sometimes it’s faster with nibs. That’s the diplomatic aspect, let’s try and look at in what situations you might prefer one over the other, enumerated as a list since my editor let it slip through;</p>
<ul>
<li>I like nibs when I have to layout UI elements from a mockup or “final” design.</li>
<li>I like nibs when I got the feeling my designer will change his mind and he can edit the nibs himself, or sit next to me dictating where the elements should be.</li>
<li>I like nibs when there’s lots of graphical elements to layout; writing code, compiling, running, editing, recompiling running is slower than seeing it WYSIWYG-ish in interface builder.</li>
<li>I like code when there’s dynamic “things” involved, except when it’s combined with one of the above, then it’s faster to just hook it up with an outlet.</li>
<li>I like code when I have to place elements relative to other elements (and I can’t use <a href="http://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/Introduction.html">auto layout</a>), as long as I cannot make it work with autoresizingMask’s in the nib.</li>
<li>I like nibs when it means there’s less -layoutSubviews or -initWithFrame: boilerplate code to scroll past.</li>
</ul>
<p>The last one is probably the most important to me. A lot of programmers seem to take a Conan the Barbarian approach to code; <a href="http://www.youtube.com/watch?v=W5K3AKl5qpc#?t=1m57s">“if you do not like code, then TO HELL WITH YOU”</a> and forgetting that when they look at the code in six months from now they’ll wasting time scrolling past code that may be important layout logic or just simple layout logic or worst of all, they’ll waste lots of time compile-run cycles trying to get their CGRect’s to align properly.</p>
<p>Code is craft, crafting is balance between getting your message across and getting your message out there. And sometimes there’s not a single right answer. That’s what makes this profession a craft, the hard part is often crafting it together in a team.</p>
Migrating from Google Apps email2011-08-05T23:37:02+02:00tag:johansorensen.com,2011-08-05:1312580222
<p>I’ve long been a paying user of Google’s Apps for Your Domain, in particular the email aspect of it. I don’t care for online documents, spreadsheets, buzz or whatever random flailing product they shove down your throat, only email. The web based gmail client showed the world how web applications should be done, for better or for worse.</p>
<p>One thing is the constant slowdowns, the 10-30 seconds waiting time for something to happen. The other being a matter of trust. Is it really wise to have so many things hosted and logged with one single provider? One who so obviously has only figured out how to earn money one one thing; namely selling your content and data for advertising? I decided it wasn’t.</p>
<p>I almost like the gmail interface, it’s one of the better web apps out there. But it still has all the flaws of a web app, along with questionable motives from its provider.</p>
<p>So in an effort to minimize risks, or at least distribute them a bit further, I decided to change my personal email provider away from Google to <a href="http://fastmail.fm">fastmail.fm</a>.</p>
<p>My personal email address (first name @ johansorensen.com) has mostly been functioning as a dumpster for all sorts of email: some personal, but mostly mailing lists and automatic notifications from various projects I’ve either worked on, with or somehow needed temporarily knowledge of the development of. In other words, and non-practical implementation of the hoarding syndrome. Which sucks. So, a few days ago I unsubscribed from every single mailing list except a few and deleted seven gigabytes of archived email (of which I’ve contributed only a few megabytes), changed my MX records and am now only using fastmail.fm through IMAP. Click. Click. Done. Almost.</p>
<p>The migration was slightly annoying as Google has some pretty strict bandwidth and/or usage limits in place, preventing you from downloading all your email at once (ding ding), so the actual migration consisted of downloading all IMAP mailboxes over a few days, and re-uploading them to the fastmail.fm IMAP account (fastmail didn’t have any arbitrary limits in place preventing me from doing with my own data as I saw fit).</p>
<p>Email was a solved problem long before Gmail. My email is now stored offline and owned by me, I can move it anywhere I want to. With modern tablets and smartphones, “access anywhere” is also a solved problem.</p>
<p>Distributing your online “cloud-ness” is a must, don’t put all your personal data with one provider, no matter how much you trust them. It’s like backups; if you have them all in one place, you’re going to get burned sooner or later.</p>
Diffing images with Core Graphics2011-04-10T18:00:00+02:00tag:johansorensen.com,2011-04-10:1302451200
<p>Few of the popular version control systems treat images as much more than the binary blobs they technically are. So it’s up to third party tools such as <a href="https://github.com/blog/817-behold-image-view-modes">Github</a> or <a href="http://www.kaleidoscopeapp.com/">Kaleidoscope</a> to provide more reasonable and user-friendly means of comparing images.</p>
<p>On a whim, let’s explore how to implement one of the ways the above tools provides image comparison with Core Graphics; difference comparison. Conceptually it means we’ll blend the new image onto the old image and subtract any pixels that are different from the other and pixels that are unchanged will be black. It’s the same blending mode you’ll find in image editors such as Photoshop and Pixelmator.</p>
<p>Doing it with Core Graphics is pretty easy: draw the old image as-is into a drawing context, change the blendmode and draw the new image and extract the composed image.</p>
<p>First we need a method for creating a <code>CGContextRef</code> from a CGImage. I like boilerplate things like this to stay out of the main method and live in its own private method:</p>
<pre><code>- (CGContextRef)createCGContextFromCGImage:(CGImageRef)img
{
size_t width = CGImageGetWidth(img);
size_t height = CGImageGetHeight(img);
size_t bitsPerComponent = CGImageGetBitsPerComponent(img);
size_t bytesPerRow = CGImageGetBytesPerRow(img);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL, // Let CG allocate it for us
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast); // RGBA
CGColorSpaceRelease(colorSpace);
NSAssert(ctx, @"CGContext creation fail");
return ctx;
}
</code></pre>
<p>In other words, pretty much standard Core Graphics creation of a CGContext. In this example we just extract the size and pixel format from the supplied CGImageRef. As per normal Cocoa memory semantics we return an object which the caller is responsible for releasing since our method starts with <code>create</code>.</p>
<p>Now for the actual blending of the two images, represented as the <code>_oldImage</code> and <code>_newImage</code> UIImage’s:</p>
<pre><code>- (UIImage *)diffedImage
{
// We assume both images are the same size, but it's just a matter of finding the biggest
// CGRect that contains both image sizes and create the CGContext with that size
CGRect imageRect = CGRectMake(0, 0,
CGImageGetWidth(_oldImage.CGImage),
CGImageGetHeight(_oldImage.CGImage));
// Create our context based on the old image
CGContextRef ctx = [self createCGContextFromCGImage:_oldImage.CGImage];
// Draw the old image with the default (normal) blendmode
CGContextDrawImage(ctx, imageRect, _oldImage.CGImage);
// Change the blendmode for the remaining drawing operations
CGContextSetBlendMode(ctx, kCGBlendModeDifference);
// Draw the new image "on top" of the old one
CGContextDrawImage(ctx, imageRect, _newImage.CGImage);
// Grab the composed CGImage
CGImageRef diffed = CGBitmapContextCreateImage(ctx);
// Cleanup and return a UIImage
CGContextRelease(ctx);
UIImage *diffedImage = [UIImage imageWithCGImage:diffed];
CGImageRelease(diffed);
return diffedImage;
}
</code></pre>
<p>Core Graphics will do the hard work of subtracting the pixels, even if the two images are of different formats or sizes, however the example here doesn’t accommodate different sizes to keep things brief.</p>
<p>I always enjoy working with Apple’s C-based APIs, they’re often well designed both when it comes to naming and functionality, while feeling strangely familiar due to their memory management semantics.</p>
Command line arguments with NSUserDefaults2011-02-20T12:30:00+01:00tag:johansorensen.com,2011-02-20:1298201400
<p>Whenever I’m in need of some quick and dirty command line arguments for a simple tool, I find using NSUserDefaults to be the easiest:</p>
<pre><code>$ ./mjau -input mjau.m
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *inputFile = [[NSUserDefaults standardUserDefaults]
objectForKey:@"input"];
NSStringEncoding usedEncoding;
NSError *error = nil;
NSString *content = [NSString stringWithContentsOfFile:inputFile
usedEncoding:&usedEncoding
error:&error];
if (error) {
fprintf(stderr, "Error: %s\n",
[[error localizedDescription] UTF8String]);
return -1;
}
fprintf(stdout, "%s\n", [content UTF8String]);
[pool drain];
return 0;
}
</code></pre>
<p>No need to bother with <code>getopt(3)</code> if you just need some simple option parsing.</p>
<p>Of course, this also means you can use it to set existing NSUserDefaults from the command line. One such useful toggle is the <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/LoadingResources/Strings/Strings.html"><code>-NSShowNonLocalizedStrings YES</code></a> default, turning non-localized strings into all caps making them easier to spot.</p>
It's the message2011-02-11T17:30:00+01:00tag:johansorensen.com,2011-02-11:1297441800
<h3>Nokia/Microsoft</h3>
<p><em>“By leveraging new synergies in partnerships that will escalate our mindshare to new global opportunities beyond this fiscal year we’re securing valuable fluff. Also, we’re still committed to Symbian and Meego. Except when we’re commited to Microsoft. Maybe next year. We’re not really sure what’s going on here either.”</em></p>
<h3>Google</h3>
<p><em>“We, the largest technology and advertising business in the world, is here to save you from an entirely different draconian future than ours. Also, we’re like open and junk and if you can’t update to Cherrybomb just tell your mom to flash her ROM!”</em></p>
<h3>Blackberry</h3>
<p><em>“Something something business something. Something QNX. Something something Flash.”</em></p>
<h3>Apple, and now HP/Palm</h3>
<p><em>“Here’s our things. This is what they do and this is what <em>you</em> can do with them.”</em></p>
<p>Simplified and obviously subjective, but which one of the above can the consumer relate to?</p>
Passing on the Gitorious torch2011-01-12T21:22:00+01:00tag:johansorensen.com,2011-01-12:1294863720
<p>My initial, and the very first, commit to Gitorious <a href="http://gitorious.org/gitorious/mainline/commit/61e76a818d8e072b48ebf093d9e72699a1e6ecfb">was august 14th, 2007</a>. It all started as scratching an itch I had about hosting my own Git repositories, eventually it was adopted by many others, then by commercial open source projects such as <a href="http://qt.gitorious.org">Nokias Qt</a> and <a href="http://meego.gitorious.org">MeeGo</a> and behind-the-firewall installations. My company did consulting and other work on custom features for Gitorious.org and custom installations.</p>
<p>Last year I started spending most of my time and energy on Objective-C and Cocoa applications again, while one of my fellow co-founders Marius kept chugging along and I did less and less work on Gitorious. Eventually we decided to formalize this whole arrangement.</p>
<p>Gitorious is now a wholly owned subsidiary of <a href="http://shortcut.no">Shortcut</a>, employing <a href="http://gitorious.org/~zmalltalker">Marius</a> and <a href="http://gitorious.org/~cjohansen">Christian</a> full time. They’re running the show and got the keys now, which is great for all parties involved; I get to focus on other things, and Gitorious receives the full attention it deserves, while being developed and maintained by a company devoted exclusively to its cause.</p>
Focusing on the Canon 7D2010-12-28T23:57:00+01:00tag:johansorensen.com,2010-12-28:1293577020
<p>Close to a decade ago I did a lot of photography, mostly black and white since I had free access to a darkroom but also with the one of the first digital flagships, the <a href="http://www.dpreview.com/reviews/nikond1/">Nikon D1</a> with a whooping 2.7 megapixels. Film vs. digital was still a topic then. Then I went and pursued interests and the prosumer part of my photography was put on standby, apart from a short stint with a Konica-Minolta digital body (shortly before Sony bought their SLR division).</p>
<p>Earlier this year I decided to give it another go, bought a Canon 500D and some lenses. Thousands of exposures later (and relearning the tricks of the trade again) I upgraded to the 7D a few weeks ago.</p>
<p>One of the nicer features if the 7D is the focusing system, while it seems Canon is still behind Nikon a bit on this, they’re making progress. My favorite customization of the 7D is moving the half-press-shutter-to-focus to the AF-On button and setting the half-press shutter button to “metering start”. This is done under the “C.Fn IV” screen.<br />
This allows me to stay in the continuous autofocus mode, while still being able to use the classic “focus, then recompose” approach, <em>without</em> losing the ability to quickly move to continuous focus on moving subjects, just by keeping the AF-On button pressed.</p>
<p>The problem I’ve always had is that by the time I’ve changed my camera focus setting from single shot to continuous autofocus whatever action I wanted to capture is long gone. Staying on continuous autofocus and remapping the focus button to the AF-On button allows me to keep the best of both worlds; focus, then recompose for single shots and continuous focus to nail the action, coupled with the 7D’s ~8 fps continuous shooting mode.</p>
Why Google TV is fundamentally flawed2010-05-21T09:57:02+02:00tag:johansorensen.com,2010-05-21:1274428622
<p>Yesterday at Google’s I/O event they announced <a href="http://google.com/tv/">Google TV</a>. It seems that Google is starting to look more and more like the Microsoft of the 90’s; partnering up with OEM vendors to get their products in front of as many people as possible. Granted, many businesses, if not all, partner with other businesses, but the Microsoft analogy seems more fitting than ever these days.</p>
<p>However I think that their approach for interaction is fundamentally flawed. In fact, I think everyone, such as Boxee and Apple TV, gets it completely wrong, especially as (or rather; if) they start to “enrich” their products with things such as web browsing and apps.</p>
<p>The problem is that they’re all viewing the TV is an input device, meaning that you interact directly with it via some kind of remote or keyboard and mouse-like device (or worst of all; an actual keyboard and mouse). This may make sense if you think of the TV as a “some kind of PC”, or because people of recent generations have always interacted with the TV via a remote. <br />
However, as soon as the interaction goes beyond simple things such as volume control and channel flipping it becomes extremely painful. If you’re browsing a website you’re in a world of pain, even the on-stage demos looked extremely painful to navigate (if you look beyond the amount of technical issues with remotes/keyboards dropping connections and the terrible presentations with other presenters mumbling on the mic in the background while the main presenter was talking, I’ve seen high-school presentations better than that).</p>
<p>The TV shouldn’t be viewed as an interactive terminal, but rather as a passive projector that receives its input from other things. Imagine instead if the remote for a storage, OS and video-decoding “media box” was an iPad(-like) tablet on which you did all the interactions and the TV simply mirrored those when you where browsing, or presented the results of such interactions, like playing a show from a playlist you just created.</p>
<p>By interacting with the device directly in front of you, through a touch interface, you’ll have a much more user-friendly interaction model, especially when browsing but also when browsing the content on the media box and creating playlists, viewing IMDb entries on the movie you’re about to watch (or while watching it). Need to show other people what you’re viewing? Just flip on mirroring directly to the TV, or select an item from the tablet to play on the TV. Easy, fast and much more enjoyable.</p>
<p>Simple interactions works fine with a simple remote, but if there’s a lot of button clicking and text input having to do it in such a detached manner from the screen is a terrible idea, and the Google TV demos was chock-full of buttons and text input.</p>
Why you wouldn't want to write your mobile application in a dynamic language2010-04-12T21:36:42+02:00tag:johansorensen.com,2010-04-12:1271101002
<p>With the current flavor of the week being a certain new section in Apple’s iPhone SDK license, there’s no shortage of sane and not-quite-as-sane commentary being thrown around. This post isn’t really about that, but it has triggered some people to argue that they should be able to write their applications in Ruby or any other interpreted language. Which shows that they either have no clue about mobile platforms, or think their users’ device is something they can just abuse at will.</p>
<p>I love Ruby, but it has little place running an entire application on a mobile device, as awesome as that would be. On a device (fruit logo engraved or not) with limited processing power, memory and battery time, do you <em>really</em> want to run an <em>entire</em> application that <del>wastes</del> spends many CPU hours just interpreting things? I’m pretty sure your potential users would have an issue with your application draining the battery much faster than your competitors application, or using substantially more memory.</p>
<p>“CPU time is cheap, programmer time is not” has become the mantra of the dynamic language world, with good reason. But it falls short when you’re no longer running on a machine with 16 or more CPU cores and eight gigabytes of RAM. In fact, if you spend some time reading through the iPhone SDK APIs you’ll find numerous encouragements to be mindful of battery use (I imagine the same being true for Android and other mobile platforms as well). CPU time and memory is the currency your application has to trade, if you waste it your users will not be too impressed with either your application or the platform as a whole (for good or for worse they reflect upon each other), both being a very bad thing for you.</p>
<p>Moore’s law of course applies to mobile CPUs as well, the difference between this years hardware and the previous one is significant regardless of manufacturer, but it’s more of a steady improvement than a giant leap forward, so you don’t quite have the luxury to trade in all your programmer time for CPU time just yet since there’s only so much to go around.</p>
<p>There’s also some interesting economics in this from the perspective of the mobile device manufacturers. Faster CPUs are more expensive, so if you can provide adequate performance at a reasonable price (cheaper CPUs means cheaper hardware for consumers, thus a competitive advantage) by only using a compiled language in your SDK (along with an API to save even more resources for things such as multitasking), then we’re suddenly talking about a significant difference in dollars and cents.</p>
<p>If your entire SDK is fairly resource intensive, such as Palm’s WebOS (both rendering HTML and interpreting javascript is a significant investment for a mobile CPU), then your device suddenly becomes much more expensive and it’ll still run out of battery in less than a day. The internet reviewers and know-it-alls does not approve of such shenanigans.</p>
A Late Note About Recent Gitorious Developments2009-05-20T10:02:42+02:00tag:johansorensen.com,2009-05-20:1242806562
<p>Just under a week I deployed a new version of <a href="http://gitorious.org">Gitorious.org</a> and announced some big changes, not only in functionality but also some things that’ll make Gitorious live long and prosper.</p>
<h3>The non-technical stuff</h3>
<p>On the non-technical side, one of the bigger changes is that Gitorious is now officially a Shortcut production. <a href="http://shortcut.no">Shortcut</a> is a company I started together with three other gentlemen at the beginning of last year and we’re now a six person company who has been making money since day one, financial crisis or not. With Shortcut maintaining and developing Gitorious.org, as well as keeping <a href="http://gitorious.org/gitorious">Gitorious open source</a>, we now have additional resources to bring the project forwards beyond what was feasible for me as a single developer doing Gitorious in my spare time.</p>
<p>A few months ago I was invited to the Qt offices and as it turned out they where running their own internal installation of Gitorious. Qt Software has been keeping busy since they got acquired by Nokia last year and one of the reasons I was invited was that they wanted to use Gitorious.org as the platform for the contribution model for the (then to be announced) LGPL licensed Qt products.</p>
<p>For the past few months Qt has funded development of <a href="http://blog.gitorious.org/2009/05/09/weve-made-a-few-changes/">new Gitorious features</a> and continues to do so. Qt has been absolutely wonderful to work with. I’ve done my fair share of consulting over the years and it’s not often you get so much freedom to work on stuff that’s both important to your client <em>and</em> to yourself as we’ve gotten working while with Qt. Something I think is really a testament to the engineering culture at Qt.</p>
<p>However, our collaboration with Qt doesn’t end with Shortcut developing Gitorious. Since Qt wanted to use Gitorious for their contribution model it was only natural that we took care of hosting it as well so they could reap the benefits of a growing community. So they <a href="http://labs.trolltech.com/blogs/2009/05/11/qt-public-repository-launched/">launched their contribution model</a> on Gitorious.org.</p>
<p>Qt is now the first “premier hosting” customer of Gitorious, meaning they get to be a an intregrated part of Gitorious with custom branding for their projects and their own portal at <a href="http://qt.gitorious.org">qt.gitorious.org</a> as well as other infrastructure needed for a big open source vendor, such as digitally signing off contribution agreements. The wonderful thing about this is that it goes full circle back to gitorious.org, with everybody benefitting along the way.</p>
<p>It’s worth pointing out that in no way is this premier hosting reserved for Qt alone. We don’t make a big fuzz about it on Gitorious.org, but we’re open for business from other companies who’d like to be a part of the growing community and in return pay for things like SLAs, support, reporting and customizations. Stay tuned for more announcements here.</p>
<h3>The technical details</h3>
<p>The Gitorious code has received some major overhauls in the past few months. Early on we decided to aim for Ruby 1.9 as the deployment target. That means for the past few months we have been developing 100% on Ruby 1.9. Why? Because <a href="http://theexciter.com/articles/why-you-should-deploy-your-next-application-on-ruby-1.html">it’s the only way we can move forward</a>. Waiting for someone else is not going to cut it, especially not when the gains are this big, making the gems that break on 1.9 work isn’t a lot of work, really. The bad news is that if no-one else has done it you have to do it yourself, just don’t take it as a road-blocker (you do know how to code ruby after all, right?) but as an excuse to actually fix those gems. We did, and as a result <a href="http://gitorious.org">Gitorious.org</a> now runs on Ruby 1.9. I can’t see what everyone is waiting for, but we seem to be one of the few actually willing to take this step.</p>
<p>If you’re curious about what other changes we’ve made to Gitorious then head over <a href="http://blog.gitorious.org/2009/05/09/weve-made-a-few-changes/">to the Gitorious blog</a> for the details on faster queuing, multiple repositories per project and namespaced user and team clones.</p>
<p>I’m really excited about the future of Gitorious and I hope you are as well. If not, then maybe <a href="http://gitorious.org/gitorious">consider submitting us a merge request with your patches</a> if you find a feature lacking or missing. After all, Gitorious is open source for a reason.</p>
Why You Should Deploy Your Next Application on Ruby 1.9 and a Rant in General2009-02-25T22:47:43+01:00tag:johansorensen.com,2009-02-25:1235598463
<p>No, it’s not speed. That’s just icing on the cake. The real reason is more obvious than that; library compatibility.</p>
<p>Dispite the fact the fact that the Ruby 1.9-preview releases has been out for months, there’s quite a few gems that’s not compatible. Rails works fine though. Can you believe that? Rails is better than most of your gems.</p>
<p>Targeting Ruby 1.9 for your next major deployment of your application (or your next application) is a perfect excuse to do some janitorial duty and make the gems not working 1.9 compatible. Don’t just report it <a href="http://isitruby19.com/">on a site</a>, fit it! Unless the gem does some hardcore thread scheduling in a C-extentions, chances are it’ll take you less than half an hour to make the tests pass on 1.9.</p>
<p><a href="http://boga.wordpress.com/2008/04/15/ruby-19-porting-notes/">This post</a> pretty much cover how you’ll fix 85% of the gems not working out of the box on 1.9, you’ll even manage to do it before your coffee is ready.</p>
<p>I know I’ll be launching my next application on 1.9. It’s the only way to move things forward.</p>
<p>But I can’t shake this feeling of that something is heading down the wrong path in the Ruby community at large. Nevermind the fact that most of us went <em><b>oh shi-</b></em> (that sound you’d make just before the universe blows up) when Ruby 1.9.1-p0 dropped and we <a href="http://isitruby19.com/">act all surprised when nothing works</a>, despite the fact that it’s been a long time coming, with several preview releases along the way. What’s more important is that the <em>art of release management</em> is slowly diminishing in certain parts of our little community. I happen to be of the persuasion that “just download whatever the current HEAD is” isn’t a proper deployment strategy. My clients sysadmins tend to agree, shuffling the responsibility of security updates back on me if it isn’t an “official” package. And that’s cool, I can wear both hats; the developer wanting the most whizz-bang for the buck and the sysadmin wanting the most stability-bang for the buck, there’s a balance in there somewhere. But there’s still this bearded guy sitting on my shoulder telling me something is wrong.</p>
<p>You see, I have little interest in spending time maintaining my own personal patches because that’s not going to scale in time and that’s not why I use open source to begin with. I have no problem in trusting that to someone who does it proper; spends a little bit of time writing up a release announcement and packages it all up on an pseudo-official community site. The problem is when that person goes away. Not so much the “why”, we all move on eventually. But more the “how”? Today, there’s a certain amount of digital paperwork involved; giving away admin permissions on rubyforge/sourceforge/whateverforge, communicating that change and so on. Of course, the post-modern developer might say “just find a git clone that works”. Except that means I have to do actual work (!), instead of putting that trust into the hands of the package maintainers, I have to go hunting for updated repositories, audit the code lightly to check if they’re high on crack or not and generally waste everyones time. There’s a reason debian (and <em>many</em> other projects) has successfully done this over the years. Yes, you may complain about the fact that they split up the ruby packages into many. But at least you have somewhere to direct that frustration, to everyones benefit, instead of simply acting nonchalantly and maintain it yourself, but only for as long as you can keep the steam running, thus degrading the system at large.</p>
<p>The rubyforge gems model may not be perfect, but damnit people, when there’s a gem update I know that it has actually been tested somewhat and it’s not just whatever random point HEAD happens to be at, at that point in time, by some random Joe who just bought TextMate.</p>
Touches and UIScrollView inside a UITableView2008-12-28T18:40:07+01:00tag:johansorensen.com,2008-12-28:1230486007
<p><strong>UPDATE:</strong> please don’t don’t do it like this, it was only needed on iPhoneOS 2.×. A lot have changed in iOS since then and you can <a href="/articles/scrollview-in-tableview-cell-properly.html">now do it properly</a></p>
<p><a href="/articles/trafikanten-for-the-iphone.html">"Trafikanten" for the iPhone</a> is the iPhone incarnation of the betabrite-style signs hanging around Oslo, providing travellers with real-time departure information on busses, trams and subways. So incorporating some of that feeling into the application, while still maintaining that iPhone look n’ feel was a crucial UI design issue for us.</p>
<p style="float:right;"><img src="http://kunder.shortcut.no/shortcutting/trafikanten-departure.png" alt="" /></p>
<p>A betabrite sign is basically a set of LED lamps that turn on and off in sequences, usually to portray text scrolling across the screen. Bringing this metaphor over to the iPhone means that the user should be able to scroll said text across the screen with his fingers. When this text lives in a subview of UITableView (which in turn is a UIScrollView subclass), there be dragons ahead!</p>
<p>The thing is, a UITableView takes completely control of the responder chain (and therefore touches) so that it can try and figure out if the user intents to scroll the scrollview, as described in the documentation overview for the UIScrollView class. So in order to be able to scroll part of a single cell sideways, while still being able to scroll the entire tableview up and down, we have to do some careful juggling with events and the responder chain.</p>
<p>Please observe this artist rendition of the view hierarchy:<br />
<notextile></p>
<pre><code> ______________________
| UITableView |
| __________________ |
| | UITableViewCell | |
| | ______________ | |
| | | UIScrollView | | |
| | |______________| | |
| |__________________| |
| __________________ |
| | UITableViewCell | |
| | ______________ | |
| | | UIScrollView | | |
| | |______________| | |
| |__________________| |
|______________________|
</code></pre>
<p></notextile>The innermost UIScrollView holds the departure times and it’s the view we want to be able to move horizontally. It’s worth pointing out that generally, for UITableViewCells, you’d want to flatten the view hierarchy as much as posible (e.g. less subviews) to achieve smooth scrolling performance. But in this case we knew that there would always be less than two dozens or so of UITableViewCells displayed in the tableview.</p>
<p>I created a UIScrollView subclass for the innermost one, that overrides all the <code>touchesMoved:withEvent:</code> and friends delegates, and then in each one try to figure out which direction an ongoing touch was heading. If the touch went up or down we’d call super, otherwise <em>pass it along the responder chain</em> (up to the tableview), so that it can do its thing:</p>
<pre><code>- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([self isTouchGoingLeftOrRight:[touches anyObject]]) {
[super touchesMoved:touches withEvent:event];
} else {
[self.nextResponder touchesMoved:touches withEvent:event];
}
}
</code></pre>
<p><code>isTouchGoingLeftOrRight:</code> tries to figure out the general direction of the gesture, by comparing the current location with the previous location, and sets an instance variable that things like <code>touchesEnded:withEvent:</code> can react to. Make sure to leave some “wiggle room” in the direction detection, since the user seldom moves completely linearly.</p>
<p>So that takes care of that right? We can go up and down, otherwise we pass it along to the next responder. Turns out there’s one more thing you’d need to pull out of the sleeve; hit detection.</p>
<p>We have to override <code>hitTest:withEvent:</code> on the actual UITableView itself. Remember that it takes control of the responder chain, so the subviews won’t get a chance to handle it first, unless we explicitly override that behavior:</p>
<pre><code>- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.decelerating) {
// don't try anything when the tableview is moving..
return [super hitTest:point withEvent:event];
}
// Find the cell
NSIndexPath *indexPathAtHitPoint = [self indexPathForRowAtPoint:point];
id cell = [self cellForRowAtIndexPath:indexPathAtHitPoint];
// if the cell has a scrollView property, it's the one we want
if (cell != nil && [cell respondsToSelector:@selector(scrollView)]) {
[[cell scrollView] setScrollEnabled:YES];
// Return the innermost scrollview
return (UIView *)[cell scrollView];
}
return [super hitTest:point withEvent:event];
}
</code></pre>
<p><code>hitTest:withEvent:</code> is responsible for telling the system which view that was <em>hit</em>, by default UITableView assumes itself (or one of its cells), so we have to figure out if the user touches our inner scrollview, and if so return that view instead.</p>
<p>I can’t help but feel that there must be a better way of doing this, rather than try and outsmart the UIScrollView’s intended behaviour. But I guess that’s the tax for straying just a tad off the beaten path.</p>
Getting Webby With It2008-12-28T17:00:46+01:00tag:johansorensen.com,2008-12-28:1230480046
<p>This site have been through its share of blogging systems over the years, wordpress, textpattern and mephisto in recent times. I finally grew tired of trying to bring my now ancient Mephisto install back to life whenever I wanted to write something (thankfully it writes completely cached pages on the frontend).</p>
<p>I decided to go with something a little more low-fi, and thus comfortable this time around. <a href="http://webby.rubyforge.org">Webby</a> is a nice little website generator framework that generates markup from static text files. No databases and myriad of frameworks to deal with, just plain old textfiles in a git repository with a post-commit hook. Another upside is the fact that I can “update” the site during offline periods, like a holiday season like this.</p>
<p>The only problem with static sites is the fact that they are static; no comments. So I’m trying out <a href="http://disqus.com">Disqus</a>, even though I hate being dependent on other services like this.</p>
<p>Here’s the script I used to write out all the existing entries from Mephisto:</p>
<pre><code>require "rubygems"
require "activerecord"
ActiveRecord::Base.establish_connection({
:adapter => "mysql",
:database => "theexciter_mephisto_prod",
:username => "user",
:password => "pass",
:host => "localhost",
})
class Content < ActiveRecord::Base; end
class Article < Content
has_many :comments
end
class Comment < Content
belongs_to :article
end
Content.find(:all, :conditions => "type = 'Article'").each do |article|
outfile = File.join(File.dirname(__FILE__), "content/articles", "#{article.permalink}.txt")
header = <<-eoh
#{'-'*3}
layout: post
title: #{article.title}
created_at: #{article.published_at.to_yaml.sub(/^-\-\-\s/, "").chomp}
filter:
- erb
- textile
#{'-'*3}
eoh
File.open(outfile, "w") do |f|
f.puts header
f.puts
f.puts article.body
f.puts
article.comments.each do |comment|
# comment markup omitted for brevity
end
end
end
</code></pre>
<p>Changing blog software is the ultimate exercise in yak-shaving, since what you really should be doing is writing posts instead…</p>