Use AI to Build or Be Built by AI

I’ve noticed there are two kinds of people using AI. You’ve got to work out which one you are as it will make big differences to your life in the long term.

A couple of months ago I found myself standing on a stage, blinded by lights. I plugged in my new Takamine guitar and positioned myself in front of a mic about to sing in front of strangers at a local pub for only the 5th time in my adult life. I was nervous but excited at testing something new.

Continue reading

Seven Years later: What I Learned from Building an AI Chatbot – Part 1

A detailed illustration of a human head in profile, filled with gears and circuits, representing a blend of mechanical and electronic elements.

Seven years ago, I embarked on an ambitious attempt to build a rudimentary rule-based AI chatbot. Frustrated by the limitations of Apple’s Siri and motivated by exciting updates to Apple’s Natural Language Processing APIs, I dreamed of building something that could understand complex queries, construct mental models of objects, and seamlessly interact with users using just their voice. Siri was merely a fancy voice control toy; I wanted to perform calculations, manipulate data and leverage the dynamic power of language to build a tiny virtual world inside my iPhone, just like an intelligent computer system from Star Trek.
Continue reading

Beautifully-formatted Times and Numbers

Example of rendered proportional numbers and monospaced numbers

Example of rendered proportional numbers and monospaced numbers

Not all font instances are created equally! In iOS text is mostly displayed using proportional fonts, meaning each character width is trimmed and varies depending on each character. This makes the text easier to read and feel more natural and you’ll notice this on characters such as ‘i’ which will often be the thinnest character compared to say an ‘m’ character. However for numbers displayed in a tabular format such as times, figures and currencies you’ll want monospaced characters so you can tidy up the layout and visually scan the data quickly. So how do you do this?
Continue reading

Core Animation stops animation on app relaunch

On one of my projects I discovered a bug in a never-ending animation I had set up. Whenever the app was suspended (such as when you multitask and open another app), on relaunching the app the animation was frozen. After some investigating, I discovered that with Core Animation you need to set a flag on the CABasicAnimation class called removedOnCompletion to NO otherwise the animation will get cleaned up when it gets suspended.

Objective-C:

CABasicAnimation *dartWiggleAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
dartWiggleAnimation.fromValue =
   [NSValue valueWithCGPoint:CGPointMake(self.center.x-DART_WIGGLE_HALF, self.center.y+DART_CENTER_H_OFFSET)];
dartWiggleAnimation.toValue =
   [NSValue valueWithCGPoint:CGPointMake(self.center.x+DART_WIGGLE_HALF, self.center.y+DART_CENTER_H_OFFSET)];
dartWiggleAnimation.timingFunction =
   [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]; 
dartWiggleAnimation.duration = 0.4;
dartWiggleAnimation.repeatCount = HUGE_VALF;
dartWiggleAnimation.autoreverses = YES;
dartWiggleAnimation.removedOnCompletion = NO;
[[nextDart layer] addAnimation:dartWiggleAnimation forKey:@"dartWiggle"];

Swift:


        let dartWiggleAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.position))
        dartWiggleAnimation.fromValue = CGPoint(x: self.center.x - DART_WIGGLE_HALF, y: self.center.y + DART_CENTER_H_OFFSET)
        dartWiggleAnimation.toValue = CGPoint(x: self.center.x + DART_WIGGLE_HALF, y: self.center.y + DART_CENTER_H_OFFSET)
        dartWiggleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        dartWiggleAnimation.duration = 0.4
        dartWiggleAnimation.repeatCount = Float.greatestFiniteMagnitude
        dartWiggleAnimation.autoreverses = true
        dartWiggleAnimation.isRemovedOnCompletion = false
        nextDart.layer.add(dartWiggleAnimation, forKey: "dartWiggle")

Does this seem like a bug or a feature?

Core Data Objects in Wrong Sections

NSFetchedResultsController is a really handy class. Use one of the default Core Data templates in Xcode and you’ll very quickly have a nice list of managed objects in a table view. With a few more lines of code you can get the NSFetchedResultsController to group your objects by sections. You do this by specifying a key-path in the class’s constructor method but there is another step that if overlooked will cause some confusion.

In a sample app I’ve created a food table that lists food in categories.

FetchedResultsController method grouping sections using a key-path:

Objective-C:

- (NSFetchedResultsController *)fetchedResultsController {
	if (fetchedResultsController != nil) {
		return fetchedResultsController;
	}

	// Create and configure a fetch request with the food entity.
	NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
	NSEntityDescription *entity = [NSEntityDescription entityForName:@"RWFood" inManagedObjectContext:managedObjectContext];
	[fetchRequest setEntity:entity];

	// Create the sort descriptors array.
	NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
	NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:nameDescriptor, nil];
	[fetchRequest setSortDescriptors:sortDescriptors];

	// Create and initialize the fetch results controller.
	NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"category" cacheName:@"Food"];
	self.fetchedResultsController = aFetchedResultsController;
	fetchedResultsController.delegate = self;

	// Memory management.
	[aFetchedResultsController release];
	[fetchRequest release];
	[nameDescriptor release];
	[sortDescriptors release];

	return fetchedResultsController;
}

Swift:


lazy var fetchedResultsController: NSFetchedResultsController<rwfood> = {
    let fetchRequest: NSFetchRequest<rwfood> = RWFood.fetchRequest()
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: "category", cacheName: "Food")
    aFetchedResultsController.delegate = self
    do {
        try aFetchedResultsController.performFetch()
    } catch let error {
        print("Unable to perform fetch: \(error)")
    }
    return aFetchedResultsController
}()

Specify a key-path

Screenshot of Food sample app in wrong order.Save and quit the app a few times and you’ll see the objects seem to be in the wrong sections. If you look closer you’ll see that the objects are actually sorted in ascending name order. On looking at the code, it seems this is exactly what we asked the program to do! After some testing it also seems to show up more often if the table is a grouped one.

As per the docs, after you specify a key-path to group each section with you also need to make sure the first sort descriptor is sorting this key-path. Add a sort descriptor and everything will work as expected.

Revised fetchedResultsController method with missing sort descriptor:

Objective-C:

- (NSFetchedResultsController *)fetchedResultsController {

	if (fetchedResultsController != nil) {
		return fetchedResultsController;
	}

	// Create and configure a fetch request with the plant entity.
	NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
	NSEntityDescription *entity = [NSEntityDescription entityForName:@"RWPlant" inManagedObjectContext:managedObjectContext];
	[fetchRequest setEntity:entity];

	// Create the sort descriptors array.
	NSSortDescriptor *typeDescriptor = [[NSSortDescriptor alloc] initWithKey:@"type" ascending:YES];
	NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
	NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:typeDescriptor, nameDescriptor, nil];
	[fetchRequest setSortDescriptors:sortDescriptors];

	// Create and initialize the fetch results controller.
	NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"type" cacheName:@"Plants"];
	self.fetchedResultsController = aFetchedResultsController;
	fetchedResultsController.delegate = self;

	// Memory management.
	[aFetchedResultsController release];
	[fetchRequest release];
        [categoryDescriptor release];
	[nameDescriptor release];
	[sortDescriptors release];

	return fetchedResultsController;
}

Swift:


lazy var fetchedResultsController: NSFetchedResultsController = {
    let fetchRequest: NSFetchRequest<rwplant> = RWPlant.fetchRequest()
    let typeDescriptor = NSSortDescriptor(key: "type", ascending: true)
    let nameDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [typeDescriptor, nameDescriptor]
    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: "type", cacheName: "Plants")
    aFetchedResultsController.delegate = self
    do {
        try aFetchedResultsController.performFetch()
    } catch let error {
        print("Unable to perform fetch: \(error)")
    }
    return aFetchedResultsController
}()