Jaanus

Musings on my life and work.

Cultural Studies Based on Salsa Tequila

Every summer must have its summer hit. Two years ago, it was “Gangnam style”. Last year, it was some Avicii stuff and maybe “What does the fox say”. I did not know this summer’s, but I just recently found out about “Salsa Tequila”. This must be it.

The song climbed the charts in Nordic countries, the Netherlands and Belgium.

All that they did is just mash together a bunch of popular Spanish words. Add an inane but catchy tune, and a silly video, and make sure the instrumental version is available, and what do you get? Infinite mashup potential, that’s what.

Indeed, many people have taken to converting this to their own language, or some other language/culture they want to make fun of. I recently spent some time going through a bunch of them, because why not. Here’s my selection of the better ones.

Kudos to the video-makers for turning this to a meme. For example, pay attention to the sound device and where it’s inserted, some of these are pretty creative.

German. (Made by some Dutch guys.)

Dutch. It’s well made (by the same guys) but I don’t know enough about Dutch culture/language to appreciate the jokes.

Polish.

Finnish. Appropriately named “Sauna and Nokia”.

Scandinavian.

Russian. These guys are lazy and didn’t even remix the song to their language. But they did make a video.

Heavy metal. (Well, it’s not a language, but cool video and music.)

“Folly of Fools”, Deceit, and Self-deception

I recently finished reading “The Folly of Fools: The Logic of Deceit and Self-Deception in Human Life” by American evolutionary biologist and sociobiologist Robert Trivers.

This is part of my reading series on the intermingling topics of security, psychology, and behavioral economics. The last book I read in the series was “Liars and Outliers” by Bruce Schneier.

I read these things because in the modern world, it is increasingly difficult to answer simple questions: What is real? What is important? Why and how do we make decisions? There are many companies who enormously profit from distorting your view of these questions. This has, of course, always been true with advertising and such, but increasingly so in the tech world.

This book is useful to reason about these topics. You’ll note mixed reviews on Amazon, and some of the one-star ones are from scientists who don’t see this book as scholarly. They’re right. It is based on science, but it is not a scientific/academic book.

It begins with an examination of evolutionary biology where the author’s background lies. While deception is “bad” as a human moral concept—thou shalt be honest and not deceive others, right—deception and the tools to counter it have been crucial in evolution and continue to be so in the modern human world.

Family and sexual topics are examined, and then onwards to many practical applications and anecdotes. I was delighted to see my own field of usability and human factors also covered, which was done in the form of discussing hierarchy and relations in airplane crews, and how obedience and self-deceptions have caused several airplane crashes.

War, history and religion are covered. In the part about false historical narratives, the discussion is around US, Palestine and Israel—probably among the most loaded topics there can be, but deconstructing the narratives from self-deception perspective can help bring rationality to the table. Modern Russia was not mentioned, but would fit into the series very well.

How I Fixed HTML List Items Jumping Around

Does this kind of thing drive you nuts?

I was working on a list of items in HTML. It is a bunch of list items, and the selection is indicated with a border. Every time you change the selection, though, the list jumps around by a few pixels. And it is terrible.

(None of this is particularly insightful to anyone who works on this stuff day to day. But I am brushing off my web skills that haven’t gotten much use for a while, and I was glad I could reason through this problem like I did. Maybe this is useful to someone else too.)

The problem is caused by how the CSS box model works. Border adds to the dimensions of the item. The non-selected items do not have a border, and are thus narrower. When a box appears, the width of the item changes, and the items after it shift right. When a box disappears, the width decreases, and the trailing items shift left.

Annoying. But easy to fix.

Now, it is possible to hack the box model in various ways. I don’t know if there is a box model that would end this jumping around without the modification that I did. My fix is dead simple though: let’s add an invisible border to all the elements. There is no border-opacity CSS property, but this can easily be done with using RGBA color for the border, instead of the more common hex format. This way, the size of the elements stays fixed, just that the border is visible for the selected one, and transparent for the others, but the border is always there.

The result is much nicer. No more jumping around.

See and play with all the code in the fiddle.

A Tragic Story That Happened to Someone I Know

I heard a tragic story recently. One of those things that you see in movies, or news. Sadly, this is real and recent.

There was a couple planning to get married. He was older and had a family before. The marriage had been planned for a long time and everyone was looking forward to it.

The bride and groom were from different countries. The wedding was set to happen in the bride’s home country. A few weeks before the wedding, the groom flew in and they got busy with the final arrangements. Everything was set, and ready, and beautiful. The bride and groom were looking forward to the wedding, and another week after it that they would spend together.

A few days before the event, the groom’s family flew in. Two days before the wedding, the guys went out partying to town. Among them was the groom’s son from a previous marriage.

They got really drunk. At one point, the team dispersed, and the son wondered off. In the middle of a night, all by himself, and wasted beyond oblivion, he stumbled across a high ledge, and fell down.

The fall was many meters, and he was stopped by some bushes. Luckily, this was in a city and not in the woods, and he was soon found. He broke most of his bones and went into a coma.

The couple decided to cancel the wedding. They would still get married eventually, but much, much later, and somewhere else, and not in the elaborate way they had planned. Now, instead of spending time together as newlyweds, they had to spend time in the hospital and insurance, arranging the son’s medical evacuation to his home country by emergency flight.

I hope they do get married eventually, but events like this clearly leave a scar.

The saddest part is that this was entirely avoidable and unnecessary. Someone’s reckless behavior caused angst and suffering not only to themselves, but the whole family.

We’re all free people. We do what we want. Just be safe and don’t be dumb. A police officer once said that if you took away “alcohol” and “stupid”, 90% of police work would disappear. And you would avoid a lot of pain and misery.

How to Correctly Configure Building Private OS X Frameworks

I’ve been recently dealing with building a private / embeddable framework on OS X. By “private framework”, I mean a framework that’s not installed system-wide, but one that’s bundled with an OS X application. Now that frameworks are also coming to iOS, it’s probably of wider interest, although this specific post is only about OS X. I spent several hours yesterday trying to get it to work, and thought to just document it, so that I wouldn’t forget myself.

TL;DR version: you need to change some build settings to the following values:

Skip Install = YES
Installation Directory = @executable_path/../Frameworks

You also need to add a “Copy files” build phase to your application target to copy the framework into its “Frameworks” group. And you also need to think about code signing, which I’m not covering here.

Okay. That was the summary. Below is an exploration of my adventures. And grab the example project.

The test project

I created a test project with Xcode 5.1.1, consisting of the default Cocoa application target. I then added a Cocoa Framework target with default settings. My test app is called HelloFramework, and the framework is simply called Hello. The code for the framework is very simple, here’s the header.

1
2
3
4
5
6
7
@interface Hello : NSObject

+ (void)helloFromClass;
+ (instancetype)sharedHello;
- (void)helloFromInstance;

@end

And the implementations imports the framework: #import <Hello/Hello.h> and just show some modal dialog boxes a la good old Javascript alerts.

Okay, so far, so good. We add the Hello framework to our app target in “Link Binary with Libraries” phase. We add a new “Copy Files” phase to bundle the framework into the application. We run it in Xcode, and everything looks good. Dialog boxes come up.

Now, things start to get a bit weird.

Archiving trouble

Let’s archive the app target. The first sign of trouble is that the archive is not of type “Mac App Archive” as expected for the Mac app, but is of type “Generic Xcode Archive”, which most of the time in Xcode means “Probably Not What You Want”. But okay. Let’s try distributing it. I only have the choice of “Save Built Products” instead of exporting the app for distribution. Let’s do this anyway.

Here’s what’s exported.

So there’s both the application and the framework. But we don’t need the framework; we just want the application. If we peek inside the app bundle, we even see that is has a correct Frameworks group with the Hello.framework nicely sitting in there.

Well, let’s try to run the app. BOOM, it crashes.

What the what? Well, if we look at the error, it says right there what the problem is.

1
2
3
4
Dyld Error Message:
  Library not loaded: /Library/Frameworks/Hello.framework/Versions/A/Hello
  Referenced from: /Users/USER/Desktop/*/HelloFramework.app/Contents/MacOS/HelloFramework
  Reason: image not found

Okay, so it’s trying to load the library from a global path. Why is that?

System vs private frameworks

There’s two kinds of frameworks. The system ones go in /Library/Frameworks, and can be used by multiple applications. The default Cocoa Framework template seems to be set up for building these. I’m not sure what their future is, though, since a lot of app distribution happens through Mac App Store and these apps are sandboxed and I don’t think they can install system frameworks.

The other kind of frameworks, private frameworks, are the ones that are embedded in the actual app bundles. These are what we’re trying to build, and are failing so far in the example.

Turns out that the install location of a framework is part of its build settings. When we look at the Installation Directory of our framework target, it is by default, unsurprisingly, set to $(LOCAL_LIBRARY_DIR)/Frameworks, which resolves to /Library/Frameworks on my system. This is not what we want. We want the framework to be embeddable in apps, and so the installation path should be relative to the app that contains the framework.

What’s the right value? I found this post to be useful when working through this topic, but they recommend changing a bunch of values that don’t seem to be necessary. I found the official doc which says, hidden inside all the other wisdom, that the Installation Directory for private frameworks should be @executable_path/../Frameworks, which looks pretty reasonable.

Great! Let’s change the installation path. When we now archive our app, we see that it creates “Mac App Archive” as expected, we can run it, and it no longer crashes. Fantastic.

There’s a warning when archiving the app target, though. Let’s get rid of that.

Fixing the last warning

The warning says:

1
Warning: Installation Directory starts with '@executable_path' but Skip Install is disabled.

It’s not super clear, but it’s clear enough if you’ve dealt with iOS app archiving troubles before and are familiar with Skip Install build setting. Basically, for a given target, Skip Install says (in the negative) whether it’s an installable target or not, which roughly translates to “Is this the app that the user will run” (in my mind, anyway). Why it needs to do that in the negative, I don’t know, calling it “Installable” would be much clearer. But just negate it in your head and it makes sense.

Skip Install = NO makes sense for system frameworks. You do want them to be installable. Although how that actually works beyond them being exported when archiving, I don’t know exactly. Anyway, in our case, for our private framework, we don’t need it to be installable so we set it to YES, and boom, the warning goes away.

The one thing to note is that there’s no nice way to archive the framework now, since it’s not an installable target. If we want to use it in another app as a pre-built thing, I just build it in Xcode and then grab the build result from the build products folder on the disk, probably with Release configuration if it’s meant to be used by others.

The mysteries remain

A few more things to note.

How is a normal person supposed to find and gather this info? I spent a few hours googling and trying out random stuff, as I wasn’t too familiar with frameworks, before I converged on this info. I think it could be a lot cleaner.

Why did the framework with wrong installation settings work when I was running the app in Xcode, and crash after exporting and trying to run the same app outside Xcode? I guess Xcode tries to be helpful here, when the framework project is part of your app workspace, and somehow masks out all these installation problems. But I think it was harmful in this case, because it really obscures the problem and shows there’s no substitute for testing the exported end product. Some of Xcode’s ways remain mysterious to me.

How does all this interact with code signing, and how will this work with Xcode 6 and iOS 8 where frameworks are now also supported? I don’t know enough about these topics right now to cover them myself, but maybe I’ll do a follow-up once I drill down into that.

Anyway, you can grab my example project here.

xScope 4 Breaks How Loupe and Guides Work Together

I do a lot of precise UI layout work. This means that I must be able to measure miscellaneous forms of comps, prototypes, and layouts. xScope has been an indispensable tool in doing this, but the latest version, xScope 4, breaks a particular behavior, that I thought might be useful to document here, so that you could also consider this when buying the upgrade.

I often use Guides and Loupe together. I position the Loupe into a particular position, and then move the Guides in for precise measurements. The magnified guides show up with the rest of the content while I make pixel-precise adjustments to the Guide positions.

For example, in the above workflow, I used the Loupe and Guides together this way, to determine that the spacing between baselines of two lines of text in OS X System Preferences, a.k.a the line height, is 28 pixels, or 14 points, since this was captured on a Retina display. (And xScope 4 lets you toggle between pixels and points in this case, which is great.)

The important point to note above is that in the z-ordering of tools, Loupe is always positioned above Guides, so it shows the magnified guides together with the content, which is absolutely my desired behavior.

The z-ordering of tools has changed in xScope 4. Sometimes, xScope 4 looks like above. And sometimes, it looks like this.

This latter look is useless for me because the guides don’t relate to anything.

See, the z-ordering of tools in xScope 4 is based on which tool is currently active. If you interact with the Loupe, it is above guides, and looks correct. But as soon as you try to start moving the guides, the guides are moved above the Loupe, and it looks incorrect.

This is a deal breaker for me and makes xScope quite useless, so much so that I have to keep using the previous version.

I have contacted xScope support team who essentially confirmed that the above description of the behavior is indeed correct. I don’t yet have an answer about whether this change in xScope 4 is a bug or a feature, or what’s next with this.

I hope that they change xScope 4 to make it possible for me to keep using it like this, with Loupe always above Guides. One way to think about it is that Loupe is a meta-tool that should magnify the output of all the other tools along with the content. I don’t want to use the guides to examine Loupe UI: I want to use them to examine the same magnified content that I am looking at with the Loupe.

I don’t care how exactly they fix it: whether they make this a preference, or default behavior, or I must do some sort of hack (change NSDefaults or whatever) to go back to the previous behavior. I just hope that there will be a way to do this. I will update this post when I get more info about this change.


By the way, if you made it this far, I’m still looking for people for closed beta of my new thing. Check it out and contact me if interested.

Why NSNotificationCenter Sometimes Deadlocks When Trying to Deliver Notifications

I’m working on something that involves some multi-threading with Grand Central Dispatch and multiple queues. Always a fun topic and a can of worms. Today, I hit a bug around this area that made me pause and think for a bit. I’m happy with myself that I was able to reason through the situation, and thought I’d post the writeup.

I’m synchronizing access to a resource using a particular serial queue. Any thread or queue posts stuff it wants to do with the resource on this queue, and it’s nicely serialized.

At one point, though, I hit a deadlock with NSNotificationCenter. Multiple queues were seemingly waiting on the lock, and the synchronization queue was blocked on posting a NSNotification. Wait, what?

When I was less experienced, these kinds of concurrency bugs used to freak me out and I didn’t really know what to do. I’d try random things and hope it worked, and sometimes it did, but I never used to develop a conclusive solution and explanation. But I guess I‘ve been doing this for a while now, and today I was able to resolve this with hypothesis-driven methodic approach. So let’s drill down. In the end, it was actually quite an easy fix. Silly, even.

One of the things that the serial queue does is post a notification about something that changed. The queue doesn’t redirect this posting anywhere else, i.e it ends up being posted on the same queue. But the posting call was blocked.

Well, let’s look at who’s consuming the notification. The only consumer was in a view controller:

1
2
3
4
5
6
someObserver = [[NSNotificationCenter defaultCenter] addObserverForName:notificationName
  object:nil
  queue:[NSOperationQueue mainQueue]
  usingBlock:^(NSNotification *note) {
      [weakSelf doSomethingInUi];
  }];

What’s going on in the main queue? The debugger shows that it is waiting to post some code in the same serial queue who’s trying to post the notification. From here on, it was easy.

So, the situation was that the serial queue was trying to post something on the main queue, while the main queue was blocked trying to post in the serial queue, and wouldn’t accept the notification. Classic deadlock.

Breaking the deadlock is easy. The technique of having notifications delivered on the main queue with the above API is usually useful, because it means you don’t have to redirect the execution yourself to another queue, but in this case it is hurting us, because the main queue may be blocked. So we’ll need to do a bit of manual legwork to more directly redirect the execution.

Also, from a requirements perspective, since the effect of the notification is that it shows some feedback to the user in the UI, it doesn’t have to execute instantly, together with the actual change. Well, it has to be fast enough for the user to perceive it as instant, but it definitely doesn’t have to happen during the same runloop.

So, let’s put the pieces together and fix the notification consumer.

1
2
3
4
5
6
7
8
someObserver = [[NSNotificationCenter defaultCenter] addObserverForName:notificationName
  object:nil
  queue:nil
  usingBlock:^(NSNotification *note) {
      dispatch_async(dispatch_get_main_queue(), ^{
          [weakSelf doSomethingInUi]; 
      });
  }];

Using nil for queue means “use whatever queue is sending this notification”, which in my case was the serial synchronization queue. And we then schedule the callback code ourselves to be execute asynchronously.

And voila, the main queue is not blocked any more and this worked fine.

My New Thing, and How You Can Help Me Make It

I’m working on a new thing.

It will be a tool for designers and developers to work better, together.

I’ve worked in the technology industry and specifically in designing and building websites, apps and UI-s for, gosh, around 15 years now. As I’ve been in different organizations, I’ve noticed some repeating trends and bottlenecks around tooling and also, more importantly, about people and how they work together. Or, how the workflow could sometimes be more fun and collaborative with better tooling.

Many organizations have by now realized that as technology becomes more and more powerful, everyone is on a more level playing field when it comes to making things. Technology and engineering, while still important, are no longer the limiting factors. Instead, you can stand out from the crowd with better design and user experience.

Yet, as a designer, you still are limited by the engineers who will build the thing for you. Some people seem to think that the answer is to have unicorns: people who can do both amazing design and then build the thing themselves. There are some people like that. If you are one of those people and can do both sides equally well, then I have nothing to teach you. You can do whatever you want. You have accomplished nirvana. Go forth and make things. You, my friend, are free.

For the rest of us, though, we still build things as designers and engineers, and need to work well together while operating with different mindsets and tools. We are united by common goals, yet sometimes have different or conflicting ideas about how to achieve those, when it comes to delivering polished apps. I am working on a tool to help make this collaboration smoother, more efficient and satisfying for all involved parties.

So, where do you come in?

I will have a closed beta period for at least several months. I want the tool to be great when it is published. There still are many hypotheses to test, and loose ends to tie up, and UI to polish, before it’s ready for a wider audience.

I am looking for users for my closed beta period, who are willing to think along and try this thing out. Here’s what I’m looking for:

  • You are working on an iOS or Mac app. Both new (not yet shipped) and existing apps (already shipped, now working on updates/next versions) are fine.
  • You have at least one designer and one developer on your team, with both of these roles fairly loosely defined. (“Designer” can be “visual”, “interaction” or whatever else your term is.) The tool is also useful for one-man builders, but I believe it will shine in teams, and that’s what I want to try out.
  • Ideally, you are based in Europe because that’s where I am, and it is easier for me to get around here. Possibly some time during the process, I would like to visit you to learn more about your team, processes, and see how the tool fits in to how you work, as well as help you get started in person. Doesn’t matter where exactly in Europe you are as long as it’s reasonably reachable by plane.

The technology is coming well together, but the project is still in its early stages, so you should be prepared to put up with the usual annoyances of early alpha-style software, like things not working, API-s breaking or changing without proper processes etc.

In return for your time, I can hopefully give you a better development process and a glimpse at how apps will be refined in the future.

If the above sounds interesting to you, then please do get in touch with me.

I will also be at WWDC in San Francisco next week so feel free to also ping or grab me to talk more in person, if you’re around SF during this time.

Far Cry 3: An Excellent Sandbox Shooter. 10/10

I recently finished the single-player campaign of Far Cry 3. According to Steam, it took me 53 hours.

Wait, what?

Yes. 53 hours.

That’s a huge amount of time to spend on a single-player game. Something like 10 hours might be more typical in my experience.

But, FC3 did take 53 hours over many calendar months. I only played a few hours at a time. And to its credit, not a single moment of it felt like wasted time, too repetitive or boring or some such. It really is a great game. I’ve recently reviewed too many “meh” games here, that I would much rather not replay. But FC3 is one that I truly do recommend if you are a fan of open-air sandbox shooters.

Here’s a video to kick it off, that shows you the highlights of the game.

Now, I’ve played the previous version before. Here’s my review of Far Cry 2. The two main gripes that I had with FC2 were that you can never really secure an area of the map, and the long time it takes to travel between regions. In FC3, both of them have been definitively fixed. When you liberate parts of the island, they are liberated for good. And fast-traveling to previous known locations is as simple as opening the map and double-clicking.

Story-wise, there is no connection to the previous parts of the series. Gameplay-wise though, this feels very much like Far Cry 2 with all of its problems fixed.

When you start to play the game, it can seem overwhelming at first. You need to acquire weapons. You need to craft some syringes. There’s so much to do! Should I follow the stories or side quests?

I can only say, relax and enjoy, because that’s what I did to gain my 50+ hours of gameplay. I did do all the side missions and explored the island. I can recommend that you try to max out on your weapons and gear before proceeding with the story quests, because it then becomes more fun and enjoyable.

How to Add a Custom Separator Supplementary View to UICollectionView

I ran into something recently that I thought might be interesting to post, since I did not see a similar example online: adding custom supplementary views to UICollectionView.

I needed to add a separator in a particular spot between the items within one section. Now, you might do this with collection view sections instead of a custom supplementary separator view. But suppose that you don’t want to re-partition your data into sections, and are just getting a flat array from upstream, and just want to work with that one.

So, without further ado, here’s what it looks like. The code is on Github if you just want to grab it. I’ll discuss some implementation details below.

Limitations

This approach has a few limitations.

  • Only one separator is supported at a time. It wouldn’t be hard to add multiple ones, but the math would be a bit more involved, and I don’t want to overcomplicate the example.
  • Only one-column layout is supported. So in a sense, you could do the same with UITableView, except that UITableView does not have such nice concept of custom supplementary views.
  • Only vertical direction is supported. It would be straightforward to make this work with horizontal scrolling as well, just switching some opeations from y/height to x/width, but it’s outside the scope of the example.

The core idea

When I was more junior and didn’t know any better, I would do horrible things when having to do something like this. Perhaps make the separator part of some cell, or, god forbid, have a custom view floating on top of the table or collection, and scroll it when the rest of content gets scrolled.

Now, though, things are different. UICollectionView and its FlowLayout do most of the heavy lifting for us, and have convenient hooks where we can customize the behavior as we want.

So, we’ll let the FlowLayout work as usual, and then, if a separator is present, just shift the items below the separator down, and draw the separator as a custom supplementary view type. FlowLayout knows only about “header” and “footer” supplementary views, but there’s no contract saying that we can’t have additional custom supplementary views in addition to these, so we do exactly that.

So let’s get started and implement this.

Register the custom supplementary view.

We don’t need a subclass or anything (although we could), we just register the ReusableView class, and handle its layout inline in the controller.

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

  // Register the separator class.
  [self.collectionView registerClass:[UICollectionReusableView class]
          forSupplementaryViewOfKind:SeparatorViewKind
                 withReuseIdentifier:@"Separator"];
}

Drawing the custom separator

After everything has been created correctly, the controller eventually gets a call to draw and configure the separator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
  UICollectionReusableView *separator = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:separatorReuseIdentifier forIndexPath:indexPath];

  if ([kind isEqualToString:SeparatorViewKind]) {
      separator.backgroundColor = [UIColor clearColor];

      if (!separator.subviews.count) {

      // … create the subview to represent the line, and set it up
      // if subviews were present, it means this work has already been done

      }
  }

  return separator;
}

The layout object

We’ll need a subclass of FlowLayout that also has some custom delegate methods, since it needs to know about where to draw the separator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@protocol JKSeparatorLayoutDelegate <NSObject>

/// Index path above which the separator should be shown, or nil if no separator is present.
- (NSIndexPath *)indexPathForSeparator;

/// Check for index path validity, since layout math doesn’t know about the model.
- (BOOL)isValidIndexPathForItem:(NSIndexPath *)indexPath;

@end



@interface JKSeparatorLayout : UICollectionViewFlowLayout

@property (nonatomic, weak) IBOutlet id<JKSeparatorLayoutDelegate> separatorLayoutDelegate;

@end

Content size

We need to return the correct content size. If we don’t do this, everything else will work correctly, but some items will be out of the view and you can’t scroll to them, you just see them when rubberbanding at the end.

1
2
3
4
5
6
7
8
9
- (CGSize)collectionViewContentSize
{
  // Increase the content size by separator height, if one is present
  CGSize s = [super collectionViewContentSize];
  if ([self.separatorLayoutDelegate indexPathForSeparator]) {
      s.height += separatorHeight;
  }
  return s;
}

The element shifting

The real work of the layout happens in the method - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect. We get a call to it with the rect that is going to screen. We can just shift the elements below the separator down here, and also add layout attributes for the separator itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
  // Grab computed attributes from parent
  NSMutableArray *attributes = [NSMutableArray arrayWithArray:[super layoutAttributesForElementsInRect:rect]];

  // Possible attributes for separator
  UICollectionViewLayoutAttributes *separatorAttributes = nil;

  for (UICollectionViewLayoutAttributes *attr in attributes) {

      // If there should be a separator above this item, then create its layout attributes
      if ([attr.indexPath compare:[self.separatorLayoutDelegate indexPathForSeparator]] == NSOrderedSame) {
          separatorAttributes = [self layoutAttributesForSupplementaryViewOfKind:SeparatorViewKind atIndexPath:attr.indexPath];

            CGRect separatorFrame = attr.frame;
            separatorFrame.size.height = separatorHeight;
            separatorAttributes.frame = separatorFrame;

      }

      attr.frame = [self adjustedFrameForAttributes:attr];

  }

  if (separatorAttributes) {
      [attributes addObject:separatorAttributes];
  }

  return attributes;
}

- (CGRect)adjustedFrameForAttributes:(UICollectionViewLayoutAttributes *)attributes
{
  CGRect f = attributes.frame;
  NSIndexPath *separatorIndexPath = [self.separatorLayoutDelegate indexPathForSeparator];

  // If there is a separator, and this item is below the separator, shift the item down
  if (separatorIndexPath) {
      if ([separatorIndexPath compare:attributes.indexPath] != NSOrderedDescending) {
          f.origin.y += separatorHeight;
      }
  }

  return f;
}

The element shifting, revisited

If you implement the above, and run it, you’ll find that it mostly works, but sometimes there is a gap in the cells.

Uh oh. What gives?

Let’s think about how layoutAttributesForElementsInRect works. It asks for layout attributes of one screenful at a time. (Actually it seems to be two screenfuls on initial run, and one screenful after that, but let’s just think about one screen.) So at first pass, the parent FlowLayout object might give us attributes for items 1-8, next 9-16, then 17-24, and so on.

But: suppose our separator is above item 4 on first screen. So the first screen contains layout for objects 1-3, separator, 4-8. And then, 8 is out of the rect so its attributes are simply discarded. They do not “carry over” to the next screen. Then, at next run, for the next screen, the parent returns attributes for 9-16, but in our case, we might want 8-15. Since 8 is not included, we’ll see a gap in its place.

To compensate for that, there’s some extra math that I’m not pasting here but that you can see in the Github project, that figures out which extra cells to add to the rect, in addition to the ones returned by parent FlowLayout.

Conclusion

UICollectionView is a fairly involved class, but has a lot of flexibility. We can let the parent classes do most of the work, and slightly customize the results from them, instead of having to do a lot of work ourselves. You’ll see that my example project implementing the above solution doesn’t actually have all that much code.