I wrote a small tool that allows to compare different scenarios when borrowing money for buying real-estate.
There are a few parameters that can be set:
How much money do I need?
How much do I want to pay back every month?
For how long is the interest rate fixed? (Longer fixing = higher rate)
Am I planning to use unscheduled repayments? (Those are mandatory, but of course lower the cost of borrowing and shorten the time until you’re debt-free.)
The tool allows to compare different selections on three of those parameters (the loan sum is currently fixed).
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.)
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.
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:
Set up S3 buckets
Set up CloudFront for these buckets
Set up a Lambda (Lambda@Edge only, this means us-east-1, N. Virginia)
Insert code (see below)
Actions/publish new version (must be versioned Lambda)
Select version and add CloudWatch triggers. NOTE: Make sure you use
the event type “origin-response”.
Ensure permissions for Lambda (logs:CreateLogStream,
logs:PutLogEvents, logs:CreateLogGroup, maybe others..)
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.
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.
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.
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.
Ok, this isn’t very complicated, even though it looks kind of cool.
The aim is to go from this:
to this:
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.