Weblog entries 2015

2015-12-11 Measuring startup time

The startup time of your app is determined by a bunch of things, but one that you can easily and directly influence, is the time spent in your AppDelegate's function didFinishLaunchingWithOptions. You may have put a number of things in there, and if you want to find out which step is taking up all that time, it's not always useful to fire up Instruments. A simple way is to just dump a bunch of logging statements.

Paste the following code at the top of the AppDelegate.swift file.

	var logTimer: CFTimeInterval = -1.0
	var logStep = 1
	func logAppProgressInit() {
		logTimer = CACurrentMediaTime()
	}
	func logAppProgress() {
		let stepTime = CACurrentMediaTime() - logTimer
		NSLog("Step %d took %.3f seconds", logStep, stepTime)
		logStep++
		logTimer = CACurrentMediaTime()
	}

Then paste the following line at the top of your didFinishLaunchingWithOptions:

	logAppProgressInit()

And the following line after each (functionally meaningful) line of code in didFinishLaunchingWithOptions:

	logAppProgress()

This code is just for a quick round of debugging, and must be removed afterwards. NSLog itself is slow and wasteful in this phase.

2015-12-08 gem install fastlane taking a long time

If you wanted to install fastlane and you issued the following command:

 $ sudo gem install fastlane

... you might have interrupted the command because it seemed to hang. In fact, it is working but may take an awfully long time due to the dependencies. Install with the verbose flag and see what's going on:

 $ sudo gem install -V fastlane
 HEAD https://rubygems.org/latest_specs.4.8.gz
 302 Moved Temporarily
 HEAD https://rubygems.global.ssl.fastly.net/latest_specs.4.8.gz
 200 OK
 ...
 ...
 ...
 Installing ri documentation for scan-0.3.2
 Parsing documentation for fastlane-1.46.1
 Parsing sources...
 100% [161/161]  lib/fastlane/version.rb
 Installing ri documentation for fastlane-1.46.1
 62 gems installed
 $

2015-11-26 Delay initialization of constant variable in Swift

Recent versions of Swift allow making a variable constant (with the let keyword), without directly initializing it.

This makes the following code possible:

	let identifier: String
	if flight == self.nextFlight {
		identifier = RosterListActivityCellReuseIdentifier.NextFlight.rawValue
	} else {
		identifier = RosterListActivityCellReuseIdentifier.Flight.rawValue
	}

Nice!

2015-11-20 Xcode tip

A nice tip for when you're pair programming and you're both sitting somewhat farther from the monitor than usual: big fonts in Xcode.

Go to Xcode -> Settings, then select the Fonts & Colors tab. There's a preset style available called "Presentation".

xcode presentation style.png

2015-11-06 Creating an OS X virtual machine

Automatically creating an OS X virtual machine is getting quite easy and automated nowadays.

If you haven't installed Homebrew, install it according to the command shown at http://brew.sh

Additionally, install Homebrew Cask:

  $ brew install caskroom/cask/brew-cask

Then install packer. If you haven't used packer before, it's a tool for automating the creation of Virtual Machine images.

  $ brew install packer

And via Cask, install vagrant and VirtualBox. Vagrant is a tool for managing development environments using Virtual Machines, i.e. starting, stopping et cetera.

  $ brew cask install vagrant
  $ brew cask install virtualbox

Now continue with rmoriz' instructions.

Check the results:

  $ vagrant box list
  macosx-10.10 (virtualbox, 0)

Initialize vagrant and start the VM:

  $ vagrant init macosx-10.10
  A `Vagrantfile` has been placed in this directory. You are now
  ready to `vagrant up` your first virtual environment! Please read
  the comments in the Vagrantfile as well as documentation on 
  `vagrantup.com` for more information on using Vagrant.

Now open the file Vagrantfile with an editor and search for the line:

  config.vm.box = "macosx-10.10"

Below, add the following lines:

  config.vm.provider "virtualbox" do |vb|
    config.vm.synced_folder ".", "/vagrant", type: "rsync"
  end

Then start the VM:

 $ vagrant up
 Bringing machine 'default' up with 'virtualbox' provider...

See if it's really running:

  $ vagrant ssh
  Last login: Fri Nov  6 04:17:05 2015
  osx-10_11:~ vagrant$ uname -a
  Darwin osx-10_11.vagrantup.com 15.0.0 Darwin Kernel Version 15.0.0: Sat Sep 19 15:53:46 PDT 2015; root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64

Alternatively, start VirtualBox, select the VM on the left side and click the Show button.

VirtualBox.png

OSX Virtual Machine.png

Congratulations! Have a drink 🍻

2015-10-30 Searching Cocoapods

Today, we were joking around in the team, and we figured it would be cool if you could simply include a Cocoapod in your iOS project to add an easter egg. All kidding aside, there's actually a nice command-line utility which allows you to search Cocoapods:

https://github.com/rochefort/cocoapods-search

No results for "easter egg", though :) But you might find one via cocoapods-roulette :)

2015-10-08 start of a day with NSDate

There's a nice new function since iOS 9, startOfDayForDate. Some examples:

To get the NSDate for this morning:

 Calendar.current.startOfDay(for: Date())

And to get the end of the previous day:

 let thisMorning = Calendar.current.startOfDay(for: Date())
 let yesterdayEvening = Calendar.current.date(byAdding: .second, value: -1, to: thisMorning)

And since iOS 8, there's isDateInToday. For example:

 if Calendar.current.isDateInToday(flight.departureDate) {
     ....
 }

2015-10-06 Testing NTP server under OS X

When you configure a different time server (NTP server) under OS X, the System Settings panel doesn't actually tell you that the address is valid or not. To check this, open a terminal window and use the ntpq command as follows:

 $ ntpq -p ntp1.example.com

The time server should list its sources and the command should display something roughly as follows:

      remote           refid      st t when poll reach   delay   offset  jitter
 ==============================================================================
 +kl10abav.xx.xxx 193.79.237.14    2 u 1030 1024  377    1.904    4.659   0.150
 -kl10ab9n.xx.xxx 193.79.237.14    2 u  924 1024  377    1.805    4.721   0.100
 *ntp1qvi.xxxxxxx .GPS.            1 u  921 1024  377   24.874   -1.048   0.330
 +ntp1tls.xxxxxxx .GPS.            1 u  705 1024  377   28.317   -1.072   0.211

If there isn't actually a time server running, then ntpq will report:

 nodename nor servname provided, or not known

2015-09-15 Xcode eating up diskspace in Library folder

Edit 2015-09-25: Don't remove simulators as suggested below. It may stop Xcode from running Playgrounds.

Xcode can eat up quite an amount of disk space. An easy fix may be to remove projects from the Projects window as well as remove Simulators. This only deletes data that can be recreated but as usual you're expected to keep a backup, like any professional does. Except Linus Torvalds, who is excused.

Before you do this, see the amount of disk space that's currently occupied by the Library folder with this Terminal command:

  $ du -sm Library/
  27966	Library/

Take the following steps:

  • Go to the Xcode menu bar, pick Window -> Projects. Remove old projects there.
  • Go to Window -> Devices. Remove simulators of old versions, or simulators which you're currently hardly using (for example because you're working on an iPhone-only app right now)

Now check the disk space again:

  $ du -sm Library/
  25227	Library/

Nice, more than 2.5 gigs cleaned up :)

2015-09-04 View all non-standard kernel extensions on OS X

If you want to list all the drivers (kernel extensions in OS X jargon) that didn't come pre-installed with OS X, open a terminal window and type the following command:

 $ kextstat| grep -v com.apple

You'll always get the following line, which are the column headers:

   Index Refs Address            Size       Wired      Name (Version) <Linked Against>

That means there are only Apple-provided drivers present on your system. As an example, I've got the Logitech OS X driver installed, plus I've got VirtualBox and two keyboard remapper tools installed:

 $ kextstat| grep -v com.apple
  Index Refs Address            Size       Wired      Name (Version) <Linked Against>
   70    0 0xffffff7f80df6000 0x46000    0x46000    com.Logitech.Control Center.HID Driver (3.9.1) <69 67 37 31 5 4 3>
  130    0 0xffffff7f82a9d000 0x28000    0x28000    org.pqrs.driver.Karabiner (10.8.0) <31 5 4 3 1>
  131    0 0xffffff7f82ac5000 0x6000     0x6000     org.pqrs.driver.Seil (11.3.0) <31 5 4 3 1>
  132    3 0xffffff7f82acb000 0x58000    0x58000    org.virtualbox.kext.VBoxDrv (4.3.24) <7 5 4 3 1>
  133    0 0xffffff7f82b23000 0x8000     0x8000     org.virtualbox.kext.VBoxUSB (4.3.24) <132 95 37 7 5 4 3 1>
  134    0 0xffffff7f82b2b000 0x5000     0x5000     org.virtualbox.kext.VBoxNetFlt (4.3.24) <132 7 5 4 3 1>
  135    0 0xffffff7f82b30000 0x6000     0x6000     org.virtualbox.kext.VBoxNetAdp (4.3.24) <132 5 4 1>

As an aside, if you want to remap some keys on your keyboard, be sure to check out the donation-supported utilities Karabiner and Seil.

2015-08-31 Centered popover on the iPad

Below is a nice centered popover on the iPad:

popover1.png

You can do this by configuring the segue in Interface Builder by:

  • (obviously) configuring as "Present As Popover"
  • Uncheck all directions
  • Set the anchor to the main view of the view controller

This is in Xcode 6.4:

xcode popover.png

It also nicely shifts up when the keyboard appears:

popover2.png

2015-08-27 Swift Switch must be exhaustive

In Swift, the switch/case statement must contain a default case, otherwise you'll get the following compiler error:

    Switch must be exhaustive, consider adding a default clause

We used to put a non-sensical log statement in there, but today, a colleague found out that you can use the break keyword.

Example:

	switch (self.type!.integerValue) {
	case CustomerTypeNormal.integerValue:
		name = "\(self.lastname?.lowercaseString.capitalizedString), \(self.firstname?.lowercaseString.capitalizedString)"
	case CustomerTypeCrew.integerValue:
		name =  "Crew"
	case CustomerTypeAnonymous.integerValue:
		name = "Anonymous"
	default:
		break
	}

2015-08-27 Open underlying SQLite database when using Core Data

During development of an app using Core Data, you often want to have a quick peep in the underlying SQLite store. Paste the following bit somewhere in your AppDelegate:

	// Log path (to manually open SQLite database) when in simulator. In the File -> Open dialog of
	// SQLPro for SQLite, type Shift-CMD-G and paste this path
	if TARGET_IPHONE_SIMULATOR == 1 {
		let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last! as! String
		println("Documents folder: \(path)")
	}

You can use the commandline sqlite client, but I like SQLPro for SQLite. Start your app, copy the string from the console in Xcode, then go to SQLPro and click the Open button. In the dialogue, type Shift-Cmd-G and you can paste the path of the Document folder of your currently running app. You can then open the SQLite database and peek inside.

2015-08-21 lastPathComponent is unavailable

You may get something like the following error when you open your Swift code in Xcode 7 beta:

  'lastPathComponent' is unavailable: Use lastPathComponent on NSString instead.

Solution is to do something like this:

  let lastPathComponent = (fileName as NSString).lastPathComponent

2015-08-07 Swift Reflect example

Halfway last month, Erica Sadun wrote a blog entry where she used the reflect() function: Swift: Just because

She just gave the code and didn't comment any further so below, I've liberally sprinkled the code with comments:

	// Define a struct to represent a point
	struct Pt {
		let x: Int
		let y: Int
		init(_ x: Int, _ y: Int) {self.x = x; self.y = y}
		init(){self.init(0, 0)}
	}
	// Declare an instance of Pt
	let p = Pt(2, 3)
	// Any means: any reference (i.e. class) or value type (i.e. struct, number, etc)
	// So this function will take an item that can be basically anything in Swift
	// and look through the names of its members to see if the string matches.
	func valueForKey(item : Any, key: String) -> Any? {
		// The built-in Swift reflect() function is used by Xcode to aid in debugging
		let mirror = reflect(item)
		// It returns an array, walk through it
		for index in 0..<mirror.count {
			// Each item in the array has a member called 0 and 1
			// This assignment is only there for readability's sake
			let child = mirror[index]
			// The .0 member is a string and it holds the name of our struct member
			if key == child.0 {
				// The .1 member holds information about our struct member
				// If it's not an optional, return its value with the .value property
				return child.1.value
			}
		}
		return nil
	}
	// Try it out
	valueForKey(p, "x")

Instead of using the valueForKey() function, you can also open a Playground and try out a few things yourself:

	let mirror = reflect(p)
	println(mirror[0])
	println(mirror[0].0)
	println(mirror[0].1)
	println(mirror[0].1.value)

swift reflect.png

2015-08-07 Core Data sum example

I had some trouble last week finding a Swift example to sum across order line entities in Core Data. In the app I'm working on, there's the usual Order and OrderLine entities, and they're exactly what you think they are: clients can use the app to shop, and when they put stuff in their shopping cart, we slowly build an order with order lines.

Note 1: in the code below, order lines are called order items because "legacy".

Note 2: the predicate in the fifth line passes self as the order. That works in our case, because the function is located in the Order class. Adjust as necessary.

Without further ado:

	func orderItemsQuantity(managedObjectContext: NSManagedObjectContext) -> Int {
		let quantityExpression = NSExpressionDescription()
		quantityExpression.name = "totalQuantity"
		quantityExpression.expression = NSExpression(format: "@sum.quantity")
		quantityExpression.expressionResultType = .Integer32AttributeType
		
		let predicate = NSPredicate(format: "order == %@", self)
		
		let request = NSFetchRequest()
		request.entity = NSEntityDescription.entityForName("OrderItem", inManagedObjectContext: managedObjectContext)
		request.propertiesToGroupBy = ["order"]
		request.resultType = NSFetchRequestResultType.DictionaryResultType
		request.propertiesToFetch = [quantityExpression]
		request.predicate = predicate
		
		var results:[[String:AnyObject]]?
		var error: NSError? = nil
		var totalQuantity = 0
		if let results = managedObjectContext.executeFetchRequest(request, error: &error) as? [[String:AnyObject]] {
			if error != nil {
				fatalError("Unresolved error \(error), \(error!.userInfo)")
			}
			if let totalQuantityDict = results.first {
				if let totalQuantityResult: AnyObject = totalQuantityDict["totalQuantity"] {
					totalQuantity = totalQuantityResult as! Int
				}
			}
		}
		return totalQuantity
	}

2015-07-09 Short list of VPS providers

Here's my 2015 short list of VPS providers:

  • TransIP Reasonable price, good CPU performance, but I've had storage reliability problems in 2013 (for which they compensated me)
  • DigitalOcean Good price, don't like their CPU performance -- starting a Grails instance took 90 seconds, as opposed to 60 seconds on a TransIP node in the AMS2 data center
  • Linode No experience with them, but good reviews from people I trust
  • Scaleway No experience with them; ARM cores
  • Vultr No experience with them; not a lot of disk space for my price range

2015-07-05 withUnsafeBufferPointer

Update 2016-10-06; a very nice (and updated for Swift 3) blog about this: http://technology.meronapps.com/2016/09/27/swift-3-0-unsafe-world-2/

I couldn't find a nice example for Array<T>.withUnsafeBufferPointer, so here's one which you can paste right into a Playground:

	//: Playground - noun: a place where people can play
	import UIKit
	var buf = [UInt8](count: 10, repeatedValue: 0)
	// Fill buffer with A to J
	for (var i = 0; i < buf.count; i++) {
		buf[i] = 65 + UInt8(i) // 65 = A
	}
	// Calculate pointer to print contents
	buf.withUnsafeBufferPointer { (pbuf: UnsafeBufferPointer<UInt8>) -> UnsafePointer<UInt8> in
		for (var j = 0; j < pbuf.count; j++) {
			let p = pbuf.baseAddress
			print(Character(UnicodeScalar((p+j).memory)))
			println(String(format:" = 0x%X", (p+j).memory))
		}
		return nil
	}

Output:

	A = 0x41
	B = 0x42
	C = 0x43
	D = 0x44
	E = 0x45
	F = 0x46
	G = 0x47
	H = 0x48
	I = 0x49
	J = 0x4A

2015-06-21 Swift 2.0 error handling

Here is a nice article by Erica Sadun on the error handling in Swift 2.0.

I really like how a try statement can be forced, i.e. try! will tell the compiler you ignore the error and this will cause a crash when the exception trips during runtime. From my Java experience, I remember that there were plenty of exceptions that basically couldn't be recovered from; you'd log them and then exit. I'm glad Apple recognized this.

The keyword defer is also interesting, delaying execution until the end of the scope. Notably, multiple statements after each other have a sequence, and these statements are executed in reverse order. On the Debug podcast, Don Melton (former Safari product manager) commented that he thought it was taken from the D programming langauge. A buddy of mine had been developing in D in the past, so I asked him what he thought about it. He remarked that he hadn't actually ever used that particular keyword in D... I wonder if I'll find much use of it.

2015-06-19 QuickLook for mobileprovision files

Craig Hockenberry wrote a Quick Look plug-in for .mobileprovision files (i.e. Provisioning Profiles). Hugely useful, because you can just select a provisioning profile and press space in Finder to see which UDIDs (devices) are included.

To debug provisioning profiles, don't let Xcode manage them. Instead when you need to run your app on a new device, take the following steps:

  • In the Member Center, go to Certificates, Identifiers & Profiles
  • Add the device for this particular App ID
  • Generate either a development or a distribution profile and download it manually
  • Use the above Quick Look plugin to verify that the downloaded profile does indeed contain the UDID of the new device
  • Double-click the new .mobileprovisioning file
  • Check with the terminal that it is now located in ~/Library/MobileDevice/Provisioning Profiles

Now go to Xcode. Since version 6, you can view provisioning profiles by going to menu Xcode -> Preferences. In the Accounts tab, select your Apple ID on the left, select your team on the right and click View Details. The new profile should be there, or else you can click the refresh button. The expiration column should show the date as exactly one year later.

If you want the .ipa file, check out this answer on StackOverflow.

2015-06-16 AutoLayout in WWDC 2015

Be sure to check out these videos on AutoLayout:

There's some stuff in there that you may already know, but they also explain new stuff. My highlights:

  • First use stackviews, then for the rest, use constraints
  • Don't add and remove constraints, activate and deactivate them
  • For text, you might want to use firstBaseline and lastBaseline
  • Use "alignment rects" if you need to ignore extra stuff in your views (like shadows)

And in part 2:

  • Use the .identifier property on views when debugging, it's a string that'll show up in the log
  • Use method in the debugger called constraintsForAxis or something
  • There's a trace method in the debugger: po [self.view _autolayoutTrace] (can show ambiguous layouts); then on the LLDB prompt: po [object exerciseAmbiguityLayout]
  • While your app is running, check the menu Debug, option View Debugging

2015-06-12 Shortcut to lock OS X

There isn't any standard function to have a shortcut key to lock your screen in OS X. The best way to do this without involving the screensaver, is the second method that's mentioned in this link: How to Quickly Lock Your Screen in Mac OS X with Keyboard Shortcut

Do take special care: the command being called won't work if you just copy/paste it from that page. Use the following line instead:

  /System/Library/CoreServices/”Menu Extras”/User.menu/Contents/Resources/CGSession -suspend

Their CMS apparently replaces the standard minus-sign with a special en-dash. The above line is correct.

2015-06-06 Dial down the transmit power of your Airport Express

In my neighborhood, there are about 20 WiFi networks visible when I click the WiFi symbol on my MacBook's top menu. Needless to say, WiFi is wonky and unstable. The usual advice of a great WiFi deployment is: multiple access points (APs) with the same SSID, where you dial down the transmit power so you cover only the exact area for that particular access point.

It's thus a damn shame that the latest version of the AirPort Utility (version 6) does not allow you to influence the transmit power. The older versions did provide an option to do so, but it doesn't run anymore on the latest versions of OS X.

Corey J. Mahler automated a workaround, which is described and can be downloaded on his website. If you do, I encourage you to send a small donation to him. Scroll down on that page and click the green dollar sign. He's been keeping this solution in the air for multiple years now.

2015-05-09 discovering bitrot with MD5

Except for modern filesystems such as ZFS and btrfs, your files aren't protected against so-called "bitrot". If, for some reason, a file is corrupted then you won't discover this and your normal backups might not have the correct file anymore. This is because the corrupted file has been backed up and your archives don't go back in time far enough.

There are a number of ways to protect against this:

  • Use a modern file system, which may not be practical at this moment.
  • Calculate hashes for your archived files and check to see if they haven't been changed somehow, with md5deep.
  • Create parity files that can not only check for damaged files but can also repair them: Parchive.

I went with the second option. md5deep is easily installable via Homebrew:

 $ brew install md5deep

To generate a file with hashes:

 $ md5deep -r -l testdirectory > testdirectory.md5deep

Explanation: -r means recursive, -l will use relative paths. This will create a file called "testdirectory.md5deep", where all files are written with their path and their hash.

To print all changed (damaged) files:

 $ md5deep -r -l -x testdirectory.md5deep testdirectory

Regularly, check the hashes against a directory that shouldn't ever change. For example, your archive of last year's family pictures. If one of the pictures got corrupted, then you know you should restore it from backup.

If you don't mind using a bit of extra space (as well as taking a bit of additional CPU time), then you can use par2. It installs nicely via Homebrew as well:

 $ brew install par2

Example command inside the directory with the files of your choice:

 $ cd testdirectory
 $ par2create par2file *

To verify:

 $ cd testdirectory
 $ par2verify par2file.par2

As an indication, a directory containing 1836 megabytes of photos and videos resulted in a couple of par2 files that took 93 megs, so about 5% of extra storage is necessary.

To go through all subdirectories and create a par2 file, I use the following one-liner on MacOS:

 $ START=$(pwd);IFS=$'\n'; for i in $(find . -type d); do if [ "$i" == "." ]; then continue; fi ;cd "${i##./}"; pwd; par2create par2file *.*; cd "$START"; done

The *.* after par2create is there to prevent subfolders being included in the parameters to par2create. I.e. with *.* we are not globbing subfolders.

2015-05-09 DenyHosts no longer available on Debian 8.0 Jessie

When I was configuring a new Debian 8.0 ("Jessie") server, I noticed the very useful DenyHosts package is no longer available in the package repository. The package "sshguard" however, is available and according to my testing, works fine.

These packages block an IP address after a number of failed login attempts. This is very useful to counter brute-force attacks on your SSH server.

2015-05-05 Stop the new Photos app from opening automatically

Since the last Yosemite update, the new Photos app will open automatically when you hook up your iPhone or iPad.

To stop this from happening, open Image Capture (available in your Applications folder), connect your iOS device, select it in the upper left, and in the lower right corner, click the arrow up. Then change the dropdown to not do anything.

Stop Photos from opening.gif

2015-05-03 HealthKit error when uploading to the App Store

Currently I'm getting an error when uploading a new binary to the App Store:

Apps that use the entitlements [com.apple.developer.healthkit] must have a privacy policy URL for [English, Dutch]. If your app doesn’t use these entitlements, remove them from your app and upload a new binary.

I've been playing around with HealthKit, but to my knowledge I removed all traces from the project in Xcode. When I have a solution, I'll post it here.

Edit: StackOverflow to the rescue

2015-04-30 edit in vi from Xcode

Purely looking at the editing part, I find vi much more powerful than Xcode. If you want to do a quick edit in vi, then simply drag the file from the project navigator to an open terminal, and edit away!

quick vi from Xcode.gif

2015-03-28 SSL security

An excellent article on SSL security, which tells you how to disable the RC4 cipher:
https://luxsci.com/blog/256-bit-aes-encryption-for-ssl-and-tls-maximal-security.html

2015-03-13 Connecting to Oodrive via FTPS on OS X

For a client, I have to save files to Oodrive. They offer several methods for uploading, one of them being FTPS, i.e. FTP-over-SSL. It turns out it's quite a hassle when you use OS X.

Taking the following steps allowed me to connect.

First install Homebrew as usual, if you haven't yet installed it. Then edit the lftp recipe:

  $ brew edit lftp

Comment out the line that says "--with-openssl" and add a new line saying

  "--with-gnutls"

Then install this recipe as follows:

  $ brew install --build-from-source lftp

See if you were successful:

  $ lftp -v
  .....
  Libraries used: Readline 6.3, Expat 2.0.1, GnuTLS 3.3.13, libiconv 1.11, zlib 1.2.5

Note the final line, where it should say "GnuTLS".

Now add the appropriate settings; create the .lftprc in your home directory, and paste the following lines (courtesy of the Reliable Penguin blog):

  set ftps:initial-prot ""
  set ftp:ssl-force true
  set ftp:ssl-protect-data true

Then, create the .lftp directory and create a file named "bookmarks" with the
following line:

  oodrive ftps://username:password@easyftp.oodrive.com:990/your/directory/here

To test the result, start lftp on the commandline and type "open oodrive",
then type "ls".

If you see the error "Fatal error: Certificate verification: Not trusted", you may want to add the following line to the .lftp settings file:

  set ssl:verify-certificate no

2015-02-06 Swift enum raw values as an array

Here's a quick example of using Swift's map() function. Here it's used to put the raw values of an enum into an array. Paste this into a playground to see the result.

	import Cocoa
	enum ContentType : Int {
		case Unknown = 0
		case PAS = 1
		case PASGENERIC = 2
		case PASROUTE = 3
		case PASBUNDLE = 4
		case TOPIC = 5
		case NEWS = 6
	}
	let contentTypeArray = [ContentType.PAS, ContentType.PASROUTE, ContentType.TOPIC]
	let intArray = contentTypeArray.map({type in type.rawValue})
	println(intArray)

Even shorter:

	let intArray = contentTypeArray.map({$0.rawValue})

How it looks in a playground:

swift enum raw values as array playground.png

2015-02-05 Swift example of a batch update in Core Data

Here's a Swift example of doing a batch update in Core Data:

	let updateRequest = NSBatchUpdateRequest("some_entity_name")
	
	let predicateIsRead = NSPredicate(format: "isRead = %@", NSNumber(bool: true))
	updateRequest.predicate = predicateIsRead
	updateRequest.propertiesToUpdate = [
		ContentItemAttributes.isRead.rawValue: NSNumber(bool: false)
	]
	updateRequest.resultType = NSBatchUpdateRequestResultType.UpdatedObjectsCountResultType
		
	var error: NSError?
	let updateResult = objectContext.executeRequest(updateRequest, error: &error) as NSBatchUpdateResult?
	if let result = updateResult {
		let count = result.result as Int
		println("Updated \(count) rows")
	} else {
		println("Error during batch update: \(error?.localizedDescription)")
	}

Note that you'll need to update the objects in memory. The above code just prints the row count. If you have stuff displayed in the user interface, use this answer on Stack Overflow to adjust the above code.

2015-02-01 Layout of a tableviewcell depending upon device orientation

Last week, a refresh of the design of my client's app called for a layout that changed depending on the device orientation. We're using AutoLayout and didn't want to use any deprecated methods.

Problem was that it called for layout changes within a UITableViewCell. I thought of a way to do this with AutoLayout but the constraints would become quite complex. Instead, I took the following approach:

  • Create a custom UITableViewCell by subclassing it
  • To this subclass, add outlets for the constraints that you want to influence
  • In the view controller, detect whether we're in portrait or landscape, then update the constraints of the cells using the IBOutlet properties

Thus the UITableViewCell subclass looks as follows:

	class CustomTableCell : UITableViewCell {
		
		@IBOutlet weak var dateLabel: UILabel!
		@IBOutlet weak var nameLabel: UILabel!
		@IBOutlet weak var trailingConstraint: NSLayoutConstraint!
		
	}

Problem is: where can you set properties all the currently visible cells? The easiest way I could come up with, is in the cellForRowAtIndexPath() function. The view controller class has the following property:

	var size: CGSize = CGSizeZero

The viewWillTransitionToSize() function stores the new screen size and asks the UITableView to reload:

	override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
		super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
		
		self.size = size
		NSLog("viewWillTransitionToSize self.size = \(self.size)")
		self.tableView!.reloadData()
	}

And when dequeueing new cells, we set the constraint its constant:

	override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCellWithIdentifier("TestCell") as CustomTableCell!
		
		NSLog("self.size=\(self.size)")
		
		cell.nameLabel.text = self.items[indexPath.row]
		if self.size.width > self.size.height { //Landscape
			NSLog("Landscape")
			cell.trailingConstraint.constant = 100
		} else { //Portrait
			NSLog("Portrait")
			cell.trailingConstraint.constant = 0
		}
		
		return cell
	}

Because viewWillTransitionToSize() is not called when the View Controller is run for the first time, I also added the following code to viewWillLayoutSubviews():

	override func viewWillLayoutSubviews() {
		super.viewWillLayoutSubviews()
	
		// Only run once, upon first display
		if self.size == CGSizeZero {
			self.size = self.tableView!.frame.size
			NSLog("viewWillLayoutSubviews self.size = \(self.size)")
		}
	}

Result:
layout uitableviewcell depending upon device orientation.gif

Download the example project: TestTableViewCellSizing.zip

Note: I created the above truly breathtaking animated gif by taking a screen recording with QuickTime Player, running the iOS Simulator, then converting the resulting .mov file with the following ffmpeg command:

  ffmpeg -i example.mov -r 15 example.gif

ffpmeg was installed via Homebrew.

2015-01-29 Update interface when app is opened

If you're developing in Swift and want to update your app's interface when it's reopened, put the following piece of code in the App Delegate:

	var appLastUsedTime = CFAbsoluteTimeGetCurrent()
	func applicationWillEnterForeground(application: UIApplication) {
		let timeSinceLastUsage = CFAbsoluteTimeGetCurrent() - self.appLastUsedTime
		log.verbose("App last used \(timeSinceLastUsage) seconds ago")
		
		//If it's been at least a day since the user accessed the app, send out a notification
		if timeSinceLastUsage > (24 * 60 * 60) {
			log.verbose("Posting notification \(MYFApplicationDidBecomeActiveAfterTimeoutNotification)")
			NSNotificationCenter.defaultCenter().postNotificationName(MYFApplicationDidBecomeActiveAfterTimeoutNotification, object: nil)
			self.appLastUsedTime = CFAbsoluteTimeGetCurrent()
		}
	}

Above the App Delegate, add the following global:

    let ApplicationDidBecomeActiveAfterTimeoutNotification = "ApplicationDidBecomeActiveAfterTimeoutNotification"

In the view controller, add the following code in viewWillAppear():

	NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateInterface",
			name: ApplicationDidBecomeActiveAfterTimeoutNotification, object: nil)

And the following code in viewDidDisappear()

	NSNotificationCenter.defaultCenter().removeObserver(self, name: ApplicationDidBecomeActiveAfterTimeoutNotification, object: nil)

Note that this notification doesn't trigger when activating the app for the first time. If you need that, add the following code to the view controller in question, in its viewDidAppear() function:

	if !self. didInitializeAtStartup {
		self.updateInterface()
		didInitializeAtStartup = true
	}

And the following class member variable:

    	private var didInitializeAtStartup = false

2015-01-20 button title truncated

There's a weird situation that when you add an image to a standard UIButton and add some space between the image and the label, the label text gets truncated:

moreinfo 1.png

The reason is that you used the image edge inset. This edge inset is not used to calculate the intrinsic button size.

For the correct steps, check my answer on StackOverflow.

2015-01-16 RestKit Error

Today I got a weird exception in the Xcode console of my iOS project:

  *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
 reason: 'Cannot perform object mapping without a source object to map from'

Turns out I had a couple of missing commas in my JSON. In case your editor doesn't validate JSON, have a look at this online JSON editor.

When I fixed that, I got the same error again. This time, I opened the file in vi, which showed that there were a number of control characters (CTRL-P) in the JSON file:

control characters in json.png

When I removed those, the error disappeared.