This is my third and final write-up on the development of an electronic programme guide app. As of part 2, the main scrolling view with the programme details, scaled to length, are displayed.
Next up I wanted to have the ability to display programme details. This seemed like it was going to be pretty straightforward: when building the per-programme view, I included a button, which is transparent, and has the same dimensions as the view itself. The first problem is associating the programme details with the button itself. There is plenty of discussion on StackOverflow about how to do this in the least offensive way. I went with a category and then object association. This allowed me to set the GUID as a property for the button.
I rebuilt the app, hit the button and… entered into a fortnight of debugging an EXC_BAD_ACCESS error. I knew what the problem was: the ARC memory management was dereferencing the button object once it was set. I tried lots of different options, such as adding the buttons to an array, set with various properties, and passing the array back to the main view controller. Nothing worked until I did more reading around the property attributes, and ended up redefining the Interface Builder defaults for the scrolling contentView to:
@property (nonatomic, strong) IBOutlet UIView *contentView;
That ‘strong’ means that everything in the view is held in memory. It has to be said that the app is very heavy on memory – as a direct consequence of that view object retention. It routinely occupies 63Mb in my testing.
Next up is the popup that is rendered. So finding the programme itself is pretty easy, using an NSPredicate based on the GUID. What proved a bit harder to deal with is if the main view [the ‘contentView’ for the scrollView] is zoomed. As you have to add the popup view to the zoomed parent, the former is going to inherit the zoom setting. I couldn’t think of an elegant way around this so I worked around it in stages. First off, the popup sits on a blurred view of the current background:
// This is quite neat: make a CGRect of the currently visible part of the scrollview: CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:contentView]; visualEffectView = [[UIVisualEffectView alloc] initWithFrame:visibleRect]; visualEffectView.effect = blurEffect; visualEffectView.frame = contentView.bounds; [contentView addSubview:visualEffectView];
Next, I register the scrollView offset in a property:
scrollOffSet = scrollView.contentOffset;
…set the zoomScale to 1, and disable the ability to zoom:
[scrollView setZoomScale:1.0]; scrollView.scrollEnabled = NO; scrollView.maximumZoomScale = 1.0; scrollView.minimumZoomScale = 1.0;
Placing the programme details subview is then relative to the currently visible rectangle:
float xForLittleView = visibleRect.origin.x + 30 ; float yForLittleView = visibleRect.origin.y + 100; CGRect progViewRect = CGRectMake(xForLittleView, yForLittleView, 350, 500);
I then have to undo the various view settings when the button to dismiss the view is touched:
[visualEffectView removeFromSuperview]; [littleView removeFromSuperview]; scrollView.scrollEnabled = YES; scrollView.maximumZoomScale = 2.0; scrollView.minimumZoomScale = 0.8; [scrollView setZoomScale:zoomScale]; [scrollView setContentOffset:scrollOffSet];
It’s all a bit clunky, but it works. I imagine that this sort of interface plumbing actually happens quite a lot behind the scenes. That said, I may have missed a trick to do it in an easier way.
I’ll call out two more details that I wrestled with. The first is a search facility on the programme title. I wanted the NSPredicate to support as many search terms as the user entered. My initial idea was to split the UITextField input on spaces, and then loop through the resulting array, appending to a stringWithFormat, where all but the first element would be in the form:
AND (title CONTAINS[c][/c] %@)
Having experimented with this, it appears that predicateWithFormat has to have the actual string passed to it, as opposed to a variable containing the string. Which I have to say strikes me as a little odd. The functional upshot of this is that I couldn’t support a variable number of search terms. I support up to three, and construct a separate predicateWithFormat for each possibility.
One final problem that I couldn’t find a fix for was implementing a UITableView’s delegates in a class that I pass the view into as a parameter. I couldn’t find a way of getting the cellForRowAtIndexPath delegate method to be called. The conclusion I came to with this was that it was setting the delegate to self, when ‘self’ was the custom object, rather than the view. It was largely a cosmetic thing [I’ve noticed that for complicated apps, I have a tendency to pile way to much code into the main viewController] so it was easily solved.
Here’s what may be the final version of the app looks like, showing a search result in the popup view, and the to/from dates for the EPG coverage:
The other buttons that I haven’t talked about explicitly are an ability to switch between days, and initiate a download of EPG data – but which are pretty straightforward. What’s still either ugly or hasn’t been fully implemented is the download progress indicator, and also the what’s-on-now quick look on the Apple Watch, as I want to have a mess around with something completely unrelated to this app: the motion detection capability.
I did add a quick fix to ‘justify’ the right hand side of the ‘table’ of programmes. Formerly they were falling off the contentView. I simply check if the rightmost width of the cell is going to be greater than the width of the contentView. If so, I set it to be the same as the width.
So that’s it. I have a pretty serviceable EPG app, which I use myself over the ad-funded variant I had before, which I guess is a fair indicator of utility. Main lesson learned: not knowing what those property attributes meant tripped me up really badly!