RecentChanges 2015-02-01 XML TXT

Latest weblog entries

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)")

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!
		cell.nameLabel.text = self.items[indexPath.row]
		if self.size.width > self.size.height { //Landscape
			cell.trailingConstraint.constant = 100
		} else { //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() {
		// Only run once, upon first display
		if self.size == CGSizeZero {
			self.size = self.tableView!.frame.size
			NSLog("viewWillLayoutSubviews self.size = \(self.size)")

layout uitableviewcell depending upon device orientation.gif

Download the example project:

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 -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 {
		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.

2014-12-15 Shadows and rounded corners

The app I'm currently working on, requires a textfield with shadow and rounded corners. That doesn't work on a UITextField; you'll have to make it transparent, then add it to a plain UIView and style that.

I got curious and tried to add shadows and rounded corners to a number of elements in UIKit. This is the code:

        import UIKit
	class ViewController: UIViewController {
		@IBOutlet weak var contentView: UIView!
		@IBOutlet weak var button: UIButton!
		@IBOutlet weak var textfield: UITextField!
		@IBOutlet weak var textView: UITextView!
		@IBOutlet weak var datePicker: UIDatePicker!
		@IBOutlet weak var toolbar: UIToolbar!
		@IBOutlet weak var imageView: UIImageView!
		override func viewDidLoad() {
		func addShadowAndCornerRadius(view: UIView) {
			let radius:CGFloat = 20.0
			view.layer.cornerRadius = radius
			view.layer.shadowColor = UIColor.darkGrayColor().CGColor
			view.layer.shadowPath = UIBezierPath(roundedRect: view.bounds,
			    cornerRadius: radius).CGPath
			view.layer.shadowOffset = CGSize(width: 0, height: 0)
			view.layer.shadowOpacity = 1.0
			view.layer.masksToBounds = true
			view.clipsToBounds = false

Below is the result in the simulator. Note that the light-blue empty element is a plain UIView.

shadow and rounded corners uikit.png

As you can see, adding shadow and rounded corners doesn't work on UIImageView, UITextField and UIToolbar. It does work on a plain UIView, UIButton, UITextView and UIDatePicker.


Weblog Archive

Weblog entries 2014

Weblog entries 2013

Weblog entries 2012

Weblog entries 2011

Weblog entries 2010

Weblog entries 2009

Weblog entries 2008

Weblog entries 2007

Weblog entries 2006

Weblog entries 2005

Weblog entries 2004

All weblog entries


Articles, chronologically (latest first). This is pretty old stuff.


Not yet finished. Maybe will never be finished. Maybe they'll get deleted. Who knows?


System administration: