{"id":1203,"date":"2015-07-06T16:58:09","date_gmt":"2015-07-06T16:58:09","guid":{"rendered":"https:\/\/zogspat.tk\/blog\/?p=1203"},"modified":"2015-07-06T16:58:09","modified_gmt":"2015-07-06T16:58:09","slug":"building-an-electronic-programme-guide-part-2","status":"publish","type":"post","link":"https:\/\/the-plot.com\/blog\/?p=1203","title":{"rendered":"Building an Electronic Programme Guide [Part 2]"},"content":{"rendered":"<p>A little later than intended, here&#8217;s the follow-up to my first posting on <a href=\"https:\/\/the-plot.com\/blog\/?p=1190\">building an EPG<\/a>.<\/p>\n<p>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\u2019m not going to dwell on this too long, as it\u2019s a well documented feature.<\/p>\n<p>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\u2019t find a way of doing it &#8211; 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.<\/p>\n<p>I finally settled on a custom implementation, with each programme being represented by its own UIView, with:<\/p>\n<ul>\n<li>the width: the duration in minutes divided by the number of minutes in the day, then multiplied by the width of the scrollview.<\/li>\n<li>the height: this is a constant for all the cells, just based on experimenting with the scrollView.<\/li>\n<\/ul>\n<p>For what it\u2019s worth, my scrollView is 4000, and the row height is 60. [Actually it\u2019s the width of the contentView the scrollView contains, but I\u2019ll refer to the scrollView from here for purposes of readability.]<\/p>\n<p>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 &#8211; a fairly blunt but effective instrument.<\/p>\n<p>When it comes to retrieving the data, which I do on a per-channel basis, I need to search using an NSPredicate:<\/p>\n<pre>NSPredicate *predicate = [NSPredicate predicateWithFormat:@\"(channel = %@) AND (startDate &gt;= %@) AND (endDate &lt;= %@)\", channelName, startDateForSearch, endDateForSearch];\r\nNSFetchRequest *allChannelDataReq = [[NSFetchRequest alloc] init];\r\n[allChannelDataReq setEntity:[NSEntityDescription entityForName:@\"Programme\" inManagedObjectContext:managedObjectContext]];\r\n[allChannelDataReq setPredicate:predicate];\r\n[allChannelDataReq setIncludesPropertyValues:NO];\r\nNSError *error = nil;\r\ntempArrayOfFilteredProgData = [managedObjectContext executeFetchRequest:allChannelDataReq error:&amp;error];<\/pre>\n<p>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\u2019ve already noted these are unsorted, so\u2026<\/p>\n<pre>NSSortDescriptor *sortByRunningOrderInt = [[NSSortDescriptor alloc] initWithKey:@\"progOrderNum\" ascending:YES];\r\nNSArray *descriptors = [NSArray arrayWithObject:sortByRunningOrderInt];\r\nNSArray *sortedProgDataForChannel = [channelData sortedArrayUsingDescriptors:descriptors];<\/pre>\n<p>\u2026where the progOrderNum is the corresponding attribute in the Entity.<\/p>\n<p>The sortedProgDataForChannel can now be looped through, for each programme:<\/p>\n<pre>- (NSMutableDictionary *)drawRect:(float)width startXPosn:(float) topLeftX rowNum:(int)rowNum forColour:(UIColor *)cellColour\r\n{\r\nNSMutableDictionary *viewAndCoords = [[NSMutableDictionary alloc] init];\r\nfloat topLeftY = rowNum * rowHeight;\r\nfloat topLeftXForNextView = topLeftX + width;\r\nCGRect rectangle = CGRectMake(topLeftX, topLeftY, width, rowHeight);\r\nUIView *thisProgView = [[UIView alloc] initWithFrame:rectangle];\r\nthisProgView.backgroundColor = cellColour;\r\nthisProgView.layer.borderColor = [UIColor blackColor].CGColor;\r\nthisProgView.layer.borderWidth = 1.0;\r\n[viewAndCoords setObject:thisProgView forKey:@\"view\"];\r\n[viewAndCoords setObject:[NSNumber numberWithFloat: topLeftXForNextView] forKey:@\"newTopLeftCoord\"];\r\nreturn viewAndCoords;\r\n}<\/pre>\n<p>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.<\/p>\n<p>That, in essence is the primary logic for building the \u2018wireframe\u2019 for the programmes. I extract the text for a given programme\u2019s 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.<\/p>\n<p>There are a couple of gotchas, and I had to take a couple of bites at this &#8211; which is a polite way of saying I made a couple of idiotic mistakes &#8211; 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:<\/p>\n<p><a href=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3084.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1207\" alt=\"IMG_3084\" src=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3084.png\" width=\"1242\" height=\"2208\" srcset=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3084.png 1242w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3084-168x300.png 168w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3084-576x1024.png 576w\" sizes=\"auto, (max-width: 1242px) 100vw, 1242px\" \/><\/a><\/p>\n<p>It really is a thing of beauty :).<\/p>\n<p>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 <em>date.<\/em>\u00a0I was then merrily plastering these onto the scrollView with the following results:<\/p>\n<p><a href=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3098.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1208\" alt=\"IMG_3098\" src=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3098.png\" width=\"1242\" height=\"2208\" srcset=\"https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3098.png 1242w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3098-168x300.png 168w, https:\/\/the-plot.com\/blog\/wp-content\/uploads\/2015\/07\/IMG_3098-576x1024.png 576w\" sizes=\"auto, (max-width: 1242px) 100vw, 1242px\" \/><\/a><\/p>\n<p>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 &#8211; in case I changed my mind later &#8211; I\u2019d do it when looping through the channel data.<\/p>\n<p>So first of all, I see if the start time of the given programme is &gt;= 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\u2019s 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.<\/p>\n<pre>-(int) calcPosOfFirstProg:(NSDate *)progStartTime\r\n{\r\nint xPos = 0;\r\n\/\/ This start x posn allows for the channel name label\r\nint offSetForChanTitle = 100;\r\n\/\/ So: the left edge of the view equates to 5am.\r\nNSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];\r\n\/\/ formatting from just HH:mm didn't work because the progStartTime has a date.\r\n\/\/ need to extract today's date from progStartTime and bake it into the viewEdgeTime...\r\n\r\n[dateFormatter setDateFormat:@\"dd\/MM\/yyyy\"];\r\nNSString *dayDateForDisplayDay = [dateFormatter stringFromDate:progStartTime];\r\nNSString *viewEdgeTimeString = [NSString stringWithFormat:@\"%@ 05:00\", dayDateForDisplayDay];\r\n[dateFormatter setDateFormat:@\"dd\/MM\/yyyy HH:mm\"];\r\nNSDate *viewEdgeTime = [dateFormatter dateFromString:viewEdgeTimeString];\r\n\/\/ Time interval gives us the number of seconds:\r\nNSTimeInterval timeDiff = [progStartTime timeIntervalSinceDate:viewEdgeTime];\r\n\/\/ All other calculations on positioning have been in minutes so:\r\nfloat minsBetweenTimes = timeDiff \/ 60;\r\nNSLog(@\"minsBetweenTimes is %f\", minsBetweenTimes);\r\nfloat minutesInDay = 1140;\r\nxPos = ((minsBetweenTimes \/ minutesInDay) * scrollViewWidth) + offSetForChanTitle;\r\nreturn xPos;\r\n}<\/pre>\n<p>That channel listing is a bit ugly &#8211; it&#8217;s why I bake the channel name into the progamme summary. What I&#8217;d really like to do is have a &#8220;floating&#8221; listing that the scrollView passes under. I&#8217;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&#8230;<\/p>\n<p>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\u2019s enough room to display it. I haven\u2019t gotten round to this yet, so late finishing programmes dangle precariously into space.<\/p>\n<p>Next up I\u2019ll 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!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A little later than intended, here&#8217;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 &hellip; <a href=\"https:\/\/the-plot.com\/blog\/?p=1203\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,5],"tags":[],"class_list":["post-1203","post","type-post","status-publish","format-standard","hentry","category-ios-development","category-tech"],"_links":{"self":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1203","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1203"}],"version-history":[{"count":3,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1203\/revisions"}],"predecessor-version":[{"id":1209,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1203\/revisions\/1209"}],"wp:attachment":[{"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1203"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1203"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/the-plot.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1203"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}