Building an Electronic Programme Guide [Part 2]

A little later than intended, here’s the follow-up to my first posting on building an EPG.

So, having marshalled the data into a structure that can be displayed, on to the guts of this app, which is the main UIView for guide itself. The pre-requisite of that main view is configuring the scrollView: I’m not going to dwell on this too long, as it’s a well documented feature.

For the formatting of the content in that classic programme guide UI, with rows of variable width cells, I initially started with looking at collectionViews, but couldn’t find a way of doing it – or certainly an amenable way. I also briefly looked at customising a tableView, which I could add multiple elements to, but rejected it for the same reason.

I finally settled on a custom implementation, with each programme being represented by its own UIView, with:

  • the width: the duration in minutes divided by the number of minutes in the day, then multiplied by the width of the scrollview.
  • the height: this is a constant for all the cells, just based on experimenting with the scrollView.

For what it’s worth, my scrollView is 4000, and the row height is 60. [Actually it’s the width of the contentView the scrollView contains, but I’ll refer to the scrollView from here for purposes of readability.]

Before I go on to calculating the [X,Y] coordinates of the top left corner of the subview, I need to recap on how the programme information is written to Core Data and then read back. At some point in the implementation as I envisage it, it will be possible for the user to both download more programme data, as only a couple of weeks worth come down at a time, and also to configure which channels to do the download for. For ease of simplicity of managing the stored data, I decided to write all of the programme data to the same Core Data entity [think database table], and when the user repeats the download, with or without changing the channel configuration, I delete the existing data – a fairly blunt but effective instrument.

When it comes to retrieving the data, which I do on a per-channel basis, I need to search using an NSPredicate:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(channel = %@) AND (startDate >= %@) AND (endDate <= %@)", channelName, startDateForSearch, endDateForSearch];
NSFetchRequest *allChannelDataReq = [[NSFetchRequest alloc] init];
[allChannelDataReq setEntity:[NSEntityDescription entityForName:@"Programme" inManagedObjectContext:managedObjectContext]];
[allChannelDataReq setPredicate:predicate];
[allChannelDataReq setIncludesPropertyValues:NO];
NSError *error = nil;
tempArrayOfFilteredProgData = [managedObjectContext executeFetchRequest:allChannelDataReq error:&error];

So this block of code is searching for hits on a given channel, startDate and endDate [attributes of the Entity], and loading them into the array. I’ve already noted these are unsorted, so…

NSSortDescriptor *sortByRunningOrderInt = [[NSSortDescriptor alloc] initWithKey:@"progOrderNum" ascending:YES];
NSArray *descriptors = [NSArray arrayWithObject:sortByRunningOrderInt];
NSArray *sortedProgDataForChannel = [channelData sortedArrayUsingDescriptors:descriptors];

…where the progOrderNum is the corresponding attribute in the Entity.

The sortedProgDataForChannel can now be looped through, for each programme:

- (NSMutableDictionary *)drawRect:(float)width startXPosn:(float) topLeftX rowNum:(int)rowNum forColour:(UIColor *)cellColour
{
NSMutableDictionary *viewAndCoords = [[NSMutableDictionary alloc] init];
float topLeftY = rowNum * rowHeight;
float topLeftXForNextView = topLeftX + width;
CGRect rectangle = CGRectMake(topLeftX, topLeftY, width, rowHeight);
UIView *thisProgView = [[UIView alloc] initWithFrame:rectangle];
thisProgView.backgroundColor = cellColour;
thisProgView.layer.borderColor = [UIColor blackColor].CGColor;
thisProgView.layer.borderWidth = 1.0;
[viewAndCoords setObject:thisProgView forKey:@"view"];
[viewAndCoords setObject:[NSNumber numberWithFloat: topLeftXForNextView] forKey:@"newTopLeftCoord"];
return viewAndCoords;
}

The idea here is to calculate the width, as described above, and create the UIView [with a couple of frills like a border], and then return the X co-ordinate for the starting point for the next view. The Y coordinate is a simple calculation: I assign the channels a number, and then multiply that by the height of each cell.

That, in essence is the primary logic for building the ‘wireframe’ for the programmes. I extract the text for a given programme’s title, start and end times, etc, and add those as labels programmatically. Again, these are calculated as offsets from the starting X coordinate and the calculated Y based on channel number.

There are a couple of gotchas, and I had to take a couple of bites at this – which is a polite way of saying I made a couple of idiotic mistakes – and the visual results of which were so bad that I decided to record them for posterity. The first is an example of not reading the documentation. I misremembered the parameters for a UIView as being X+Y for the top left corner, and then X+Y for the bottom right corner. This resulted in the following:

IMG_3084

It really is a thing of beauty :).

Next up was dealing with the simple matter of the start of the day. The NSPredicate I use extracts per-channel programme data for start times on a given date. I was then merrily plastering these onto the scrollView with the following results:

IMG_3098

Obviously a refinement is required, so first port of call was to make 5am the equivalent of the left edge of the scrollView. I decided that, rather than hardwiring this into the search parameter for the NSPredicate – in case I changed my mind later – I’d do it when looping through the channel data.

So first of all, I see if the start time of the given programme is >= 5am. If not I disregard it. If it is, I then calculate the offset for the X coordinate. This is based on calculating the difference between the start time of the programme and 5am in minutes. I then divide this by the number of minutes in the day and multiply it by the width of the scrollView. There’s an additional offset [which actually applies to all of the programme UIViews] for a channel title list down the left hand side of the scrollView.

-(int) calcPosOfFirstProg:(NSDate *)progStartTime
{
int xPos = 0;
// This start x posn allows for the channel name label
int offSetForChanTitle = 100;
// So: the left edge of the view equates to 5am.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
// formatting from just HH:mm didn't work because the progStartTime has a date.
// need to extract today's date from progStartTime and bake it into the viewEdgeTime...

[dateFormatter setDateFormat:@"dd/MM/yyyy"];
NSString *dayDateForDisplayDay = [dateFormatter stringFromDate:progStartTime];
NSString *viewEdgeTimeString = [NSString stringWithFormat:@"%@ 05:00", dayDateForDisplayDay];
[dateFormatter setDateFormat:@"dd/MM/yyyy HH:mm"];
NSDate *viewEdgeTime = [dateFormatter dateFromString:viewEdgeTimeString];
// Time interval gives us the number of seconds:
NSTimeInterval timeDiff = [progStartTime timeIntervalSinceDate:viewEdgeTime];
// All other calculations on positioning have been in minutes so:
float minsBetweenTimes = timeDiff / 60;
NSLog(@"minsBetweenTimes is %f", minsBetweenTimes);
float minutesInDay = 1140;
xPos = ((minsBetweenTimes / minutesInDay) * scrollViewWidth) + offSetForChanTitle;
return xPos;
}

That channel listing is a bit ugly – it’s why I bake the channel name into the progamme summary. What I’d really like to do is have a “floating” listing that the scrollView passes under. I’ve used key value observers a lot in the past, so one approach would be to set a KVO on the delegate that returns the current scroll scale value, and to resize based on the changes in scale. One to come back to…

It would, of course, be necessary to do the same for the right hand of the scrollView, to make sure that for a given cell, there’s enough room to display it. I haven’t gotten round to this yet, so late finishing programmes dangle precariously into space.

Next up I’ll talk about creating the pop-up view for the programme details, including navigating an ARC related memory error, which took me a loooooooong time to figure out!

Adding SATA 3 Support to a Mac Pro

Caveat: be careful with your expensive, delicate computer. This has all the characteristics of a hack, but it was wildly successful for me. There may also be a much more sensible way of doing this out there. YMMV.

I have a mid 2010 spec Mac Pro, with the 6 core Westmere chip. I’ve been running out of disk space for about half of the elapsed time since I upgraded to a SATA 2 SSD about 3 years ago. Amazon are doing a pretty good deal on a Samsung SATA 3 SSD at the moment so I thought I’d take a punt on upgrading.

The long and short of it is that I’ve hacked together a pretty cheap option, which has given me a 13x speed up on sustained write speeds.

I went for this card [which appears to have risen in price by a couple of quid since last week], on the basis of seeing a reference to a particular chipset [ASM1061] on a forum somewhere along the line.

I have to admit straight off the bat that I’d never worked with PCIE before, and it took me a while to work out that the card was missing a power supply. This led me on a merry dance to figure out how to get power into my SSD. There are a couple of raised interfaces on the card which may be power outlets, but I’ve never seen anything like them before. Having looked at a tutorial on how to install a USB 3 device, I thought I was going to end up having to take the fan out, and get some sort of MOLEX to Mac 4 pin style power adapter cable. Having then hit on the fact that your stock Mac Pro [of my era at least] comes fitted with a single occupied bay of 4 designed for hard drives with SATA [2] interfaces, I bought one of these. Here’s the really hacky bit: the extender cable comes with a couple of clips either side to stabilise the connection. You have to clip one of them off, and plug it into the SATA interface of your choice.

And it works. And it’s bootable [a gotcha for some PCIE cards], and it handles resurrection from sleep. I verified that it was registered as a SATA 3 [look for the negotiated link speed of 6 Gb/sec] and used BlackMagic Disk Speed Test to verify a sustained write speed of 354 Mb/sec.

While there are integrated cards that you slot your SSD straight into, this turned out to be cheaper. That said, the new SSD is currently sitting loose inside the machine. There are integrated ‘sleds’ that you can screw the SSD into, and then slide into one of the four disk bays. I haven’t seen one that’s less than about £20. I also haven’t experimented with any of the 3 spare interfaces [2 of which are external] on the card: I’ve no idea if you can RAID up two SSDs on the same card, for instance. I’d be interested to find out!

Location Display – Arduino Based Clock

Once I finally got everything working with the weather station clock, predictably, it started to lose its appeal. So I’ve taken the bones of it and refactored to display my current location. The source of the location data is another little iOS app, this time using background fetch.

I may split the various moving parts of this into a couple of posts. There are 6 in all, most of which I’ve cannibalised from the weather app. I’ll only talk about the new [or what I think are more interesting pieces]. And as I’ve mentioned before, messing with this kind of data has personal privacy implications. While people may not be interested, it’s best to make it difficult. The components are:

  • The iOS app generating the data.
  • This posts to my hosting service via a rest interface called Arrest-MySQL.
  • There is a database behind the REST interface, which is on my hosting service.
  • In order to limit the number of annotations / pins on the iOS app, I trim them with a SQL script embedded in PHP [the latter is a restriction imposed by my hosting service]. I call this as a nightly cron job from the Pi.
  • I then display this via pretty much the same Arduino sketch that for the clock for the weather data.
  • This receives data via Serial USB from the Pi based on a ‘nohup’ Perl script. I start and check this is running every 10 minutes using cron.

There is a 7th: we are going to South East Asia in a while, so I’ve written another little iPad app which my mother in law can use to track us with. My idea; I thought she’d be interested :).

I’ll start with the iOS app. One of the most immediate points to note about the iOS 7 background fetch capability is that, in comparison to using the Significant Change mechanism I’ve implemented before, it’s a lot gentler on the battery. While we are comparing the two, the fetch has two frequency settings: ‘none’ and ‘a lot’. I guess this might be something that Apple might increase the granularity on at some point in the future.

I guess there is a shopping list of features that I’ve picked up along the line which raise this above ‘bare bones’. The first is that if there is no network, I allow a manual ‘pin drop’ which writes the current coordinates to Core Data. Each time the background fetch fires [only when the network is available], I try to post these via the REST interface.

I also attempt to use Google’s GeoCoding API, which converts the latitude and longitude into a string for the given address, which then subsequently appears on the map annotation, or on the clock’s LCD display. I have to admit that in both instances, this is a bit of a dog, due to character limits, and custom annotations with mapping – well, while I’ve done it before, it’s substantially more effort than what this app warrants . I’m also not too sure what Google is going to return if we happen to be dropping location data in a jungle location with fantastic WiFi [!].

One more confession on the Core Data approach: originally I thought I would store failed writers to the database based on Reachability telling me I was offline when the background event fired. Actually, iOS does this for you: no network, no attempt to do a background fetch. While this is obvious, it never occurred to me based on prior experience with the Significant Change functionality which percolates away based on cell tower activity.

One or two more points of interest for the mobile app. There are limits on what you can do with the network in terms of libraries used / delegates available, so no AFNetworking. Actually for what I want to do in the foreground, AFNetworking is much too rich: for instance, I had to Google to find a workaround to its rather sticky caching. Also, something that really stumped me until I thought about it: if you are dynamically returning results [such as from the REST interface] don’t bother trying to plumb in a network progress indicator, unless your server is also setting a Content Length header. That’s a couple of hours of my life I’m not getting back :).