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.
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 $
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!
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.
Congratulations! Have a drink 🍻
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 :)
Here's a couple of pointers to popular libraries:
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) { .... }
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
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:
Now check the disk space again:
$ du -sm Library/ 25227 Library/
Nice, more than 2.5 gigs cleaned up :)
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.
Below is a nice centered popover on the iPad:
You can do this by configuring the segue in Interface Builder by:
This is in Xcode 6.4:
It also nicely shifts up when the keyboard appears:
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 }
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.
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
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)
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 }
Here's my 2015 short list of VPS providers:
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
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.
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:
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.
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:
And in part 2:
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.
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.
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:
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.
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.
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.
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.
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
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
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:
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.
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:
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)") } }
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.
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
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:
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.
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:
When I removed those, the error disappeared.