Lukas Z's Blog

Calculating the Deltas for performBatchUpdates

Poly Art Title

Time for a code snippet post.

If you have collection or table view that can change in unpredictable ways, and you wish to animate the changes, then you will usually use the performBatchUpdates method of UICollectionView and UITableView.

This method takes a block, and in that block you are expected to account for all the changes in your datasource. For instance, if the number of elements returned in a section is 20 and after the performBatchUpdates-call its 10, they you are required to either delete 10 rows or items within that block, or delete 11 and add 1, or delete 20 and add 10, and so forth. The numbers must add up, otherwise your app will crash with an NSInternatlInconsistencyException. (You can theoretically catch it but I haven’t tried because I suspect it will lead to many odd bugs if you do.)

Ok, so I googled for a generic way to calculate the necessary accounting, and I found this answer on StackOverflow.

The StackOverflow soltuon is presented in pseudocode, so here’s how it would look like with Swift.

import Foundation

enum ArrayAction {
    case added(Int)
    case deleted(Int)
    case moved(Int, Int)
    
    static func operationsPerformed(arrayBefore before: [Int], arrayAfter after: [Int]) -> [ArrayAction] {
        var arrayActions = [ArrayAction]()
        
        var map1 = [Int: Int]()
        var map2 = [Int: Int]()
        
        for (index, element) in before.enumerated() {
            map1[element] = index
        }
        
        for (index, element) in after.enumerated() {
            map2[element] = index
        }
        
        for key in map1.keys.sorted() {
            let index1 = map1[key]
            let index2 = map2[key]
            
            if index2 == nil {
                let deleteAction = ArrayAction.deleted(index1!)
                arrayActions.append(deleteAction)
            }
            else if index1 != index2 {
                let moveAction = ArrayAction.moved(index1!, index2!)
                arrayActions.append(moveAction)
                map2.removeValue(forKey: key)
            } else {
                map2.removeValue(forKey: key)
            }
        }
        
        for key in map2.keys {
            let addAction = ArrayAction.added(map2[key]!)
            arrayActions.append(addAction)
        }
        
        return arrayActions
    }
}

So as you can see there’s an enum called ArrayAction that represents the rows/items added, deleted and moved. Here’s how I use that method in my ViewController:

 private func performCollectionViewOperations(block: () -> (), completionBlock: (()->())? = nil) {
        collectionView.performBatchUpdates({
            let filteredBefore = self.dataSource.elements.map { $0.index }
            block()
            let filteredAfter = self.dataSource.elements.map { $0.index }
            
            for action in ArrayAction.operationsPerformed(arrayBefore: filteredBefore, arrayAfter: filteredAfter) {
                switch action {
                case .added(let i):
                    self.collectionView.insertItems(at: [IndexPath(item: i, section: 0)])
                case .deleted(let i):
                    self.collectionView.deleteItems(at: [IndexPath(item: i, section: 0)])
                case .moved(let i, let j):
                    self.collectionView.moveItem(at: IndexPath(item: i, section: 0), to: IndexPath(item: j, section: 0))
                }
            }
        }) { _ in
            completionBlock?()
        }
    }

And that private method is used like that:

    self.performCollectionViewOperations(block: {
        // do some adding, removing and moving of elements here.
    }) // not using the completionBlock here

And that’s pretty much it.

Where do I use this code and might it have anything to do with the title picture? Glad you asked. :P

It’s used in Poly Art, a game a friend and I made for iOS. It’s free to play and we’d be happy if you gave it a try!

(I really had a hard time calculating the deltas in the game, because I stubbornly avoided the generic solution. Instead I was trying to anticipate what changes were possible, but as any programmer sooner or later learns: Even the simplest things can get very difficult if you use just a few of them combined.)

How to Scrape Wikiquote

Wikiquote logo

I like to use the command line tool fortune which displays a random quote from a database of files.

I wanted to add quotes from Wikiquote. But it turns out that Wikiquote is difficult to parse and the API is no help. (The API gives you a structured document but the actual content is just one large wall of text that has to be parsed just like the HTML page.)

So after giving up on scraping the webpage I had the idea to use Wikiquote’s edit page.

Here’s how it works: Let’s say you want the quotes from the Richard Feynman page. Open it on your browser, tap edit, and you get the edit page for Richard Feynman.

Voilà. It’s much easier to parse! Simply take all lines from the <textarea> that start with one or two asterisks (depending if you want the source) and then filter out a few things like formatting and lines that only contain quotes. A much better scraping experience.

So yeah, that’s my idea/innovation. I wrote a scraper in ruby that you can find on Github which generates the files needed by fortune. Check out the README there.

(Wikiquote image taken from here.)

AWS: How to Change S3 Response Headers

I have S3 buckets that I use to serve public assets with (behind CloudFront, with a custom domain name). However the response always included the header field “Server” with a value “AmazonS3”. I didn’t like to publish the fact that I was using S3 (because what for?) and I wondered If I could change that string.

It’s not entirely straightforward, but not difficult either.

Here’s how I did it:

  1. Set up S3 buckets

  2. Set up CloudFront for these buckets

  3. Set up a Lambda (Lambda@Edge only, this means us-east-1, N. Virginia)

  4. Insert code (see below)

  5. Actions/publish new version (must be versioned Lambda)

  6. Select version and add CloudWatch triggers. NOTE: Make sure you use the event type “origin-response”.

  7. Ensure permissions for Lambda (logs:CreateLogStream, logs:PutLogEvents, logs:CreateLogGroup, maybe others..)

  8. Set up trust relationship for lambda role, see below.

Note that depending on you CloudFront setup it can take a while for your changes to become visible. I used the “Invaldations” feature to invalidate the cache for some urls.

Lambda Code:

'use strict';

exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;
    response.headers['server'] = [{
        key: 'Server',
        value: 'Whatever you like',
    }];

    callback(null, response);
};

Trust Relationships:

"Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]

This is all based on this useful blogpost, definitely check it out: https://nvisium.com/resources/blog/2017/08/10/lambda-edge-cloudfront-custom-headers.html

How to Tag Stages in AWS API Gateway

I just realised that I cannot add tags to my API:Gateway-stage using CloudFormation templates. However, it’s possible to tag using the command line. But this requires knowledge of the id of the stage - which you don’t get when you use the !Ref function on the Stage.

But luckily it can be constructed easily:

aws apigateway tag-resource --resource-arn arn:aws:apigateway:<REGION-NAME>::/restapis/<RESTAPI-ID>/stages/<STAGE-NAME> --profile <PROFILE-NAME> --tags sometag=somevalue,anothertag=anothervalue

Perhaps this helps someone.

Setting AWS APIGateway Alarms for Certain URLs Only

Quick post on how to set Alarms in AWS CloudWatch that trigger for certain URLs only. This assumes you use API Gateway.

Bascially, you can set Alarms in 3 ways only: Either on the entire API, on the API and the Stage OR on API, Stage, Resource and Method. For the latter two Enable Detailed CloudWatch Metrics has to be checked in the Logs section of Stage properties.

That’s basically it. You need all four dimensions if you want to filter by URL and you need enable Cloudwatch metrics on the stage.

Here’s a YAML snippet showing the declaration of an alarm.


ApiGateway5xxAlarm:
Type: "AWS::CloudWatch::Alarm"
Properties:
  ActionsEnabled: true
  AlarmActions: [!ImportValue {"Fn::Sub": "SomeNotificationTopicArn"}]
  AlarmDescription: "Some Description"
  AlarmName: "alarm-name"
  ComparisonOperator: GreaterThanThreshold
  Dimensions:
    - Name: ApiName
      Value: "my-api"
    - Name: Stage
      Value: "LATEST"
    - Name: Resource
      Value: "/some_resource/{somePathParameter}/{anotherPathParameter}"
    - Name: Method
      Value: "GET"
  EvaluationPeriods: 1
  MetricName: 5XXError
  Namespace: AWS/ApiGateway
  OKActions: [!ImportValue {"Fn::Sub": "SomeNotificationTopicArn"}]
  Period: 60
  Statistic: Sum
  Threshold: 0
  TreatMissingData: notBreaching

(I didn’t check the YAML but it should be fine.)

iOS: Bugfix for Using the Sharing-extension of WhatsApp

If you, like me, started to recently get the error “This item cannot be shared. Please select a different item” when sharing to Whatsapp, then this might help you.

You are probably sharing a text-item. In my case it was a text containing an URL.

Instead, I now share the NSURL-object (or an array of objects, in which one is an NSURL object).

I am relatively sure this is a bug in the latest version of Whatsapp, but as long as it persists, this might be a workable workaround.

(Mini-) Cheat-Sheet: Date to String and String to Date in Java

It’s Java-time.

I need this from time to time, and each time I have to look it up. A little reminder for myself.

How to create a date:

Date object representing “now”:

Date date = new Date();

Date object representing a specific time using Calendar:

Calendar cal = Calendar.getInstance();
cal.set(2012, Calendar.DECEMBER, 21, 14, 21, 33);
Date date = cal.getTime();

Date object representing a specific time using SimpleDateFormat:

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
Date date = sdf.parse("21/12/2012 14:21:33");

How to format a date:

Using SimpleDateFormat:

Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
String s = sdf.format(date);

There’s many more ways to do this, but these are just common ways.

Here is an overview of the format strings.

  Meaning Type Example
G Era designator Text AD
y Year Year 1996; 96
Y Week year Year 2009; 09
M Month in year Month July;Jul;07
w Week in year Number 27
W Week in month Number 2
D Day in year Number 189
d Day in month Number 10
F Day of week in month Number 2
E Day name in week Text Tuesday; Tue
u Day number of week (1 = Monday, …, 7 = Sunday) Number 1
a Am/pm marker Text PM
H Hour in day (0-23) Number 0
k Hour in day (1-24) Number 24
K Hour in am/pm (0-11) Number 0
h Hour in am/pm (1-12) Number 12
m Minute in Hour Number 30
s Second in minute Number 55
S Millisecond Number 978
z Time zone General time zone Pacific Standard Time; PST; GMT-08:00
Z Time zone RFC 822 time zone -0800
X Time zone ISO 8601 time zone -08; -0800; -08:00

It’s taken from here.

WebGL: Creating a Landscape-mesh With Three.js Using a PNG-heightmap

Ok, this isn’t very complicated, even though it looks kind of cool.

The aim is to go from this:

Flat heightmap example png

to this:

Three.js landscape

The idea is that the brighter a pixel on the heightmap is, the higher the elevation of the resulting mesh is. So the first step is to get the values for all the pixels in the PNG. This is accomplished by drawing the image onto a canvas, and then getting the pixel data from it. It looks something like this:

canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var data = canvas.getContext('2d').getImageData(0,0, img.height, img.width).data;

The next step is to create a planar mesh with ThreeJS and go through all the pixel and set their z-value, their elevation, according to the color of the pixel.

for (var i = 0, l = geometry.vertices.length; i < l; i++)
{
  var terrainValue = terrain[i] / 255;
  geometry.vertices[i].z = geometry.vertices[i].z + terrainValue * 200 ;
}

Please keep in mind that in this case the width in pixels is 1 more than the number of segments in the plane. (If there’s one segment, theres 2 vertices, two segments, 3 vertices, and so forth..) If that’s not the case more work is required, because the landscape will be “skewed” if height and width dont correspond correctly.

And that’s it.

I also afterwards call

geometry.computeFaceNormals();
geometry.computeVertexNormals();

so the shading according to the light-source is correct.

You can get the code from Github.

On Programmatically Checking Linux-passwords

Klaus Trainer has just posted a short article on how to write a short program, that checks usernames and passwords on a Linux machine.

I was at first confused why it works, since there was no salt. But in fact it’s there, inside the entry in /etc/shadow.

Here’s the format:

$<HASHING-METHOD>$<SALT>$<HASHED-PASSWORD (base64)>

So for example if we have a row like this:

$6$qUgyc2fC$hljmJlJU7TV4gm8GDZd51eZnxNgWp3rUJ49kSblRLssLqxux5K.xSIcIn2QjL27jXOREBfXiB1WES3SEIPHk10

Then the 6 means that SHA512 was used, and qUgyc2fC is the salt.

macThesaurus: A Mac-app Written With Node-webkit

I decided to play around with node.js, specifically with node-webkit.

Node-webkit allows programmers to write desktop-apps with Javascript, and even package them for distribution, for example via the Mac App Store. (Check out this list of apps written with it.)

To play with it, I’ve decided to write a tiny thesaurus-app.

  • It should lookup and display synonyms and related words using a (German) thesaurus via the JSON-API at openthesaurus.de.
  • To be really useful, it should be possible to bring the app to the foreground using a keyboard shortcut. In my case it’s Shift+Alt+T.
  • It should be possible to dismiss the app by tapping ESC

It worked quite nicely. The app doesn’t really feel like a web-app, even though it utilizes web-app technologies. And I could even create a binary for the App Store.

Here’s a short video of the app in action:

And here’s the source on Github.