Recall These Thoughts
Inanity from Scott Lougheed, PhD

I’m currently working on a small Mac menu-bar based application for myself. I wanted to present information in an NSTableView, but the standard cell-based row cell offered by NSTableView on macOS didn’t offer me enough flexibility for my needs. The basic cell-based NSTableView looks similar to this:

Classic Table View This is a super clear and useful way to display lots of data in a sortable table. For my project I needed something a bit more aesthetically pleasing for use in a context where there would always be a fixed number of table cells in a fixed order.

I needed to display multiple, differently-sized and formatted NSLabels and NSImages. View-based NSTableViews are perfect for this, allowing for something like this crude example:

Custom View-based Table On iOS this is accomplished by creating static cells, which is not only fairly straightforward, but also the subject of dozens of reasonably current tutorials and Stack Overflow posts.

Accomplishing this in AppKit is not nearly as straightforward (a truism), and all but one of the examples I could find online were around a decade old and written in Objective-C (also a truism about Mac development). There was a single example (the link to which I have since lost, and it is hard to find!) in Swift, albeit in Swift 3, since which time there have been numerous changes to the Swift language and to how Interface Builder works in Xcode.

Nevertheless, between the lone Swift example and a few ancient Objective-C examples I was able to get it sorted out and create a custom view-based NSTableView. My struggle inspired me to write an updated tutorial on the process in Swift 5.

A note: I am a very novice programmer and am self-taught. As such, my approach may be clumsy and at times downright wrong. I’m happy to receive knowledgable and kind feedback so I can improve the tutorial.

To begin, open a new Swift language project for macOS in Xcode. I’ll walk us through the process to build the custom table of users in the image above.

Creating Our Classes

Let’s start by creating the necessary classes and any additional files we might need.

ViewController()

First we need to have our ViewController() class conform to NSTableViewDelegate and NSTableViewDataSource.

Let’s also make a property of our ViewController() class containing some data to display. We’ll use a dictionary here with name and role keys.

let users = [["name":"Scott Lougheed", "role":"CEO"], ["name":"Ari Khari", "role":"President"], ["name":"Tandi Lori", "role":"Leader"]]

CustomTableCell()

Now add a new Cocoa class file.

Create a new Cocoa class

Name the file CustomTableCell (obviously this could be whatever you like for your own project) and make it a subclass of NSTableCellView.

name cocoa class and subclass

We don’t need to do anything with this file yet, to let’s move on to our interface.

Building the Interface

Setting up the table

Switch to Main.storyboard and add a Table View to the existing view. Resize the Table View to fill the entire area. For this example we only want one column that will span the entire table width.

In the Document Outline, select the Table View (make sure you haven’t accidentally selected the Bordered Scroll View or any other superviews).

Select Table View in IB

In the Table View’s Attributes Inspector:

  • Make sure Content Mode is set to View Based and the number of columns is set to 1 and uncheck the “Headers” option.
  • Whether we want them or not, I like to enable “Alternating Rows” while I’m working on layout in Interface Builder because it’s easier to see the hight of each row and the position of elements within, so go ahead and check that box while we’re here. We can always un-check this after laying out our cell if we prefer.

While we have our Table View selected:

  • ctrl-drag from the Table View in the Document Outline to View Controller and set the View Controller as the Table View’s Delegate and Data Source.

Setting the table's delegate and data source

Finally, select the size inspector for the Table and set the Row Size Style to Custom and the row height to 60.

Back in the Document Outline, select the Table Cell View. In the Identity Inspector:

  • Set the Class to our previously-created CustomTableCell class.
  • Set the Identifier to userCell so we can address it from our ViewController class. Identity Inspector for Table Cell View

Select the Size Inspector:

  • Set the Height to 60. This won’t impact the interface at build time but does make the row in Interface Builder match the row height we set for the Table View, which makes placing our UI elements much easier.

We should now have a table in Interface Builder that looks roughly like this: table in interface builder

Building our UI

Select the existing label that says “Table View Cell” and delete it. From the Library (cmd-shift-L), place as a child of the Table Cell View an Image View, two labels, and a Push Button. You’ll know you’ve placed these correctly if they appear as a child of the Table Cell View in the Document Outline. Select the image:

  • Set the height and width constraints to be 50.
  • Set a constraint so the leading edge is be 0 points from the leading edge of the superview.
  • In the Attributes Inspector, set the Image attribute to be NSUser (or whatever image you like!).

Select one of the labels:

  • Set a constraint so the leading edge is 5 points from the trailing edge of the image view.
  • 0 pts from the top of the superview.
  • Trailing edge is 25 pts from the trailing edge of the superview.
  • In the Attributes Inspector, set the title to “User Name” and the found to System Bold 15.

Select the other label and position it just below the User Name label.

  • Change the title to “Role”.
  • Set the leading edge to align with the leading edge of the User Name label.
  • Set the top edge to be 8pts from the bottom of the User Name label.

Select the push button:

  • In the Attribute Inspector, set the title to “Email User”
  • In the Size Inspector, set the trailing space to be 10pts from the trailing edge of the Superview and 5pts from the bottom.

Return to the Role label one last time and set a constraint of 8pts from the trailing edge of the label to the leading edge of the Email User button.

Creating Outlets and Actions

Using the assistant editor, create an outlet for each label (userNameLabel and roleLabel, respectively) in CustomTableCell. Create an action for the Button, which we’ll just leave as a stub. Your CustomTableCell() class should look like this:

Custom table cell code For our purposes we don’t need to create an outlet for the table in our View Controller but don’t forget to do this if you’re going to be doing anything serious with your table.

Configuring Our TableView

Time to get back to code. Switch to your ViewController file. We need two methods in here to get our TableView up and running: numberOfRows(in:) and tableView(_:viewFor:row:).

numberOfRows(in:)

This method tells our tableView how many rows to create, and it’s as simple as returing the size of our users array:

    func numberOfRows(in tableView: NSTableView) -> Int {
        return users.count
    }

tableView(_:viewFor:row:)

This is a bit more elaborate, here’s the code, with explanations below.

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard let userCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "userCell"), owner: self) as? CustomTableCell else { return nil }
        
        userCell.userNameLabel.stringValue = users[row]["name"] ?? "unknown user"
        userCell.roleLabel.stringValue = users[row]["role"] ?? "unknown role"
        
        return userCell
    }

The first line create a new userCell and typecasts it as our CustomTableCell (if you wanted to use a standard cell, you would cast this as an NSTableCellView). You’ll recall we set the Identifier of our TableCellView to “userCell”. Make sure you pass NSUserInterfaceItemIdentifier(rawValue: "userCell") as the argument to the makeView method call. This seems like a bit of a hacky way to make things work so if you know of a way to avoid this, please get in touch! (Remember when I said I was a total novice!?)

The second and third lines seed the labels with the appropriate data from our array of users using row as the subscript to access each array index based on its position in the table (e.g., row 0 accesses the 0th index in the user array). We further subscript it using the dictionary key for the value we want to display.

At this point you can use the ! operator to force unwrap these values. Even though we’re all-but guaranteed to not get a nil back in this particular context, I’ve still chosen the less risky option of using the nil coalescing ?? operator to print a string in case for some reason we get nil back.

The final line returns the userCell to the tableView delegate.

At this point you can run your app and you should see your pretty table with your custom cells!

If you’ve had trouble following these directions or need to check your work please download the Xcode project files .


Today during a listen-through of Jimmy Eat World’s discography (as I do with bands from time to time), it was time to listen to Chase This Light (2007), the band’s fifth record. Chase This Light had, for a decade, been the point at which Jimmy Eat World and I diverged. For years, as the band released new records, I had difficulty coming around to their evolving sound. While I continued to buy their subsequent records, none of them made it into high rotation even though I regularly revisited Clarity (1999), Bleed American (2001), and Futures (2004).

My taste in music has evolved considerably over the last 18 years or so, of course. Many artists also evolve over the course of their careers, sometimes in ways that jibe with their original fans, other times their evolution takes them into new territory that their original fans don’t dig. I never disliked later Jimmy Eat World records, it was just that, as they were released, those subsequent releases just weren’t meeting me where I was at that time. They couldn’t get their hooks in me.

During today’s listen-through I found myself really getting into Chase This Light in a way that I hadn’t gotten into any Jimmy Eat World record since Futures. It got it’s hooks in me in a way it didn’t when it came out in 2007.

Out of curiosity I calculated how old Jim Adkins (frontperson and primary songwriter) would have been when Chase This Light was released. It turns out he would have been roughly 32 years old at the time the record was released, and likely would have written the majority of the records in the preceding couple of years.

Well wouldn’t you know it, right now I am also roughly 32 years old. And boy doesn’t it just make sense that a record written by a 30-something, about a 30-something’s troubles, might appeal to a 30-something listener in a way that it wouldn’t have appealed to a 20 year old listener.

While there are still some elements of the record’s production that bother me, the Chase This Light nevertheless really clicked with me today. While I can’t imagine it’s solely because I’m more willing to listen to 32 year old Jim Adkins now than I was 13 years ago, I have to assume that’s a large part of what’s at play. My musical tastes have evolved considerably over that time, becoming considerably broader, and I am much less of a pretentious asshole (because boy, in my teens and early 20s I was often a real pretentious asshole). So too have my life circumstances and the emotional landscape I inhabit changed.

Chase This Light didn’t meet me where I was in 2007, but it seems to be meeting me where I am in 2020. I don’t mind that one bit, because adding Chase This Light to my rotation doesn’t mean I like Clarity, Bleed American, and Futures any less, it’s just more music for the listenin’!.


This website is created using Jekyll. Jekyll is great for making simple, static websites like this one. Jekyll makes composing new posts super easy since posts are just basic markdown files. What does take a bit of effort is actually publishing a post. I run Jekyll locally, so there is no “publish” button I can just press and let loose my words on the world like users of fully hosted CMS (e.g., Wordpress) can. Every time a Jekyll site is modified it needs to be rebuilt and the modified files uploaded to the server.

This was just enough friction to seriously discourage posting or otherwise updating this site. Until recently my workflow looked something like this:

alt-text

  1. Open the local directory for my site in BBEdit.
  2. Use an Alfred workflow to run in terminal jekyll serve --livereload, and then launch a new Safari tab displaying the locally-served version of this site to see changes as they happen.
  3. Make the desired changes/compose a new post
  4. Use an Alfred workflow to run bundle exec jekyll build to build the final version of the site
  5. Launch Transmit
  6. connect to my host and navigate to the correct remote and local directories,
  7. upload the files via SFTP using Transmit’s Sync function.

alt-text

While I made certain Jekyll-related tasks a bit easier with the help of Alfred, such as live-monitoring changes and doing the final build with just a couple keystrokes, I never quite got around to streamlining that fifth step. That fifth step was where the friction was. Uploading the site always took longer than I’d like, requiring me to babysit Transmit while it did its thing and make sure I was in the correct directory locally and remotely so I don’t accidentally delete the entire contents of the server. In general it was just a lot of clicking and waiting.

I finally sat down to streamline that fifth step. If I could write a quick bash script to copy the files from my local machine to the server, I could invoke it with Alfred and hopefully make more of the upload process automatic. My first instinct was to use scp in the command line, which could have worked with a few tweaks to how my Jekyll site gets build locally. sftp could also have worked but seemed a bit more challenging to write into a standalone script. rsync ended up being my weapon of choice. It’s secure, it only transfers modified and new files, and is not fussy in terms of scripting.

The result is a much faster and less error-prone workflow:

  1. Open the local directory for my site in BBEdit.
  2. Trigger (jserve) an Alfred workflow to run in terminal jekyll serve --livereload, and then launch a new Safari tab displaying the locally-served version of this site to see changes as they happen.
  3. Make the desired changes/compose a new post
  4. Trigger (jbuild) an Alfred workflow to run bundle exec jekyll build to build the final version of the site
  5. Trigger (jpush) an Alfred workflow to execute the rsync.

The entire site is updated in a matter of seconds, whereas the previous method took minutes for Transmit to perform its sync task – and that’s after I did all of the launching and navigating.

The one potential downside – though not a major concern for me personally - is that in order for the rsync to work in a totally hands-off manner, I had to set up key-based SSH authentication without a passphrase (a straightforward guide is available from Linode). This makes life a lot easier but the absence of a passphrase means it is a bit less secure.

Look forward to more posts and updates on here courtesy of this new streamlined workflow. If you know of other, potentially better ways to accomplish this, reach out!


From the PHAC on June 2nd 2018:

Currently, there are 59 cases of Salmonella Enteritidis illness in eight provinces: British Columbia (6), Alberta (8), Manitoba (9), Ontario (14), Quebec (19), New Brunswick (1), Nova Scotia (1), and Newfoundland and Labrador (1). Ten people have been hospitalized. No deaths have been reported. Individuals became sick between March and May 2018. The average age of cases is 34 years, with ages ranging from 1 to 82 years. The majority of cases (61%) are male.

Earlier this year I wrote about how the response to Salmonella in frozen breaded chicken products has been sluggish relative to the comparatively modest outbreak caused by E. coli in Beef processed by XL Foods. At the time of that post there were 86 illnesse’s including one death, attributed to frozen breaded chicken products between 2015 and 2017. On June 2nd 2018 we can add 59 more illnesses to the roster, bringing the total to 145 illnesses in 3 years attributed to the same type of product.

The XL Foods-related outbreak resulted in 18 known illnesses, zero deaths, and severely tarnished career of the agricultural minister at the time. In this case, in 2015, industry introduced some new labelling initiatives after the 2015 outbreaks, but it wasn’t until March 2018 that the Canadian government announced it was working with industry to address the issue. It is dismaying that we are three years and 145 illnesses into this ordeal. Government and industry should have begun the hard work in 2015, not 2018. The proposed changes which require “industry to implement measures at the manufacturing/processing level to reduce Salmonella to below detectable amounts in frozen raw breaded chicken products” cannot come soon enough. At this rate I’m fairly sure we’ll see another outbreak before the 12 month deadline set by the CFIA is reached.


A recent survey from Dalhousie University conducted by Sylvain Charlebois and colleagues examined food recalls and food safety in Canada. While the study raises some important issues worthy of further investigation, I take issue with one area of concern cited by the study’s authors. The authors of the survey were troubled by their finding that most consumers did not feel they were among the “most responsible for ensuring food safety in Canada”, and that most consumers believed contamination generally took place before food reached their homes. Since cross-contamination in the home has been implicated in past foodborne illness outbreaks, one could conclude that consumers are not living up to their responsibility as guarantors of food safety. However, as I see it, placing primary responsibility for food safety on consumers is the troubling part for number of reasons.

First, it is important to disambiguate the location at which a foodborne hazard is introduced and where illness takes place. Of course most illness takes place in homes and restaurants since that is where food is typically consumed. However, this does not provide any indication of whether the product arrived at the home or restaurant contaminated or if it was contaminated during preparation. Illnesses take place where consumption occurs. Contamination can take place anywhere in the supply chain from farm to fork. Unless a consumer introduces a pathogen to otherwise uncontaminated food, the initial contamination that made cross-contamination possible had to have originated before reaching the consumer’s kitchen.

The evidence of where, on average, pathogenic contamination takes place, is not especially clear (see for example Jacob and Powell 2008). At a minimum, what little we do know suggests there is no strong evidence to support a claim that the majority of contamination takes place in the home. But since contamination originating in the home would not necessitate a recall, we can reasonably assume that any pathogen-related recall must be due to contamination that took place prior to the consumer. This is an issue currently plaguing frozen breaded chicken products, which until recently had been largely written off as a problem of consumer mis-handling.

Second, a consumer has no realistic way of personally assessing or verifying the activities of that supply chain. A consumer has rather limited agency in this regard. Instead, a consumer has to mostly to go on trust, brand reputation, regulations, laws, labels, and certifications to make up for the opacity and impersonality of the modern food supply chain (and no, blockchain isn’t a great solution either). In contrast, when a food packer or processor wants to ensure the safety and quality of incoming ingredients, they will often perform audits of their supplier’s facility and perhaps their supplier’s suppliers. They may also specify contractually-binding minimum quality attributes and bacterial loads. The packer or processor may also do their own tests of incoming raw ingredients. Consumers simply do not have the privilige of other supply chain participants to audit and inspect their suppliers, nor do they likely have the resources and knowledge to perform microbiological tests on products they buy.

It would, for example, be mildly absurd to blame the consumer for watery and limp lettuce purchased at the supermarket because that lettuce was likely grown, harvested, and processed half a world away. Why then should a consumer be blamed if the same lettuce was contaminated with E. coli? The consumer has about as much control over the quality of lettuce as they do the pathogen load. It may even be the case that they have less control over the pathogen load, since limpness—unlike microbes—can conceivably be detected by a human without the aid of technology or highly specialized skills.

Third, using cross-contamination by consumers as justification for making them the primary responsible party suggests that providing contaminated food to consumers is only a problem when a consumer “mis-handles” the product (e.g., by handling it in such a way as to cause cross-contamination). This echoes the longstanding reluctance of the food industry to take responsibility for pathogen reduction (see for example the beef industry’s opposition to new E. coli regulations in the 90s and here). However, history has shown that food producers often can do more to reduce pathogens in their products. The poultry industry, for decades, resisted the idea that they could take steps that would reduce the presence of Salmonella in poultry on retail shelves. When Walmart began requiring that its poultry suppliers introduce greater pathogen controls in order to remain on the retailer’s shelves, industry complied, and indeed the percentage of products contaminated with detectable levels of Salmonella decreased from 17% to 4%.

Finally, hazards such as mislabelling or undeclared allergens—which account for the majority of food recalls—by definition occur upstream of the consumer, and are neither detectable nor remediable by the consumer. In this instance responsibility for avoiding an adverse reaction or anaphylaxis cannot possibly be placed on the shoulders of a consumer. If we are thinking about consumer responsibility and recalls, then the number one cause of recalls is well beyond the remit of consumers. If we want to stay within the domain of pathogen-related recalls, the water continues to be murky. The number one product category responsible for foodborne illness as of the last few years is fresh produce, which unlike meat which we often associate with microbial hazards, is almost exclusively consumed raw. This leaves very little opportunity for consumers to take any preventative measures whatsoever.

There is a massive disparity in agency and power between consumers and almost every other member of the supply chain. Consumers are not empowered to be guarantors of food safety. Placing primary responsiblity for food safety on the shoulders of consumers is akin to blaming a passenger for not preventing a plane crash caused by pilot error. Consumers do have an important role to play in preventing foodborne illness, but food safety is a shared responsibility, and focusing on consumers may distract from the many ways that industry and regulators could and should be doing better.


In 2012, XL Foods issued an international recall of 1800 products potentially contaminated with E. coli O157:H7 and linked to 18 illnesses and zero deaths. The recall and outbreak were protracted affairs, with significant media attention beginning in October 2012 and continuing into 2013.1

The XL Foods recall and outbreak was also a political and public policy fiasco. The premier of Alberta, where XL Foods operated, was criticized for not responding to early signs of trouble.2 Meanwhile, political opponents fought to have Agriculture Minister Gary Ritz3 resign. Ritz was being blamed for crippling cutbacks the CFIA’s enforcement capabilities, was painted as generally lacking competence and leadership, and as “having failed consumers badly”.

In the wake of the recall and outbreak, there was significant debate and relatively prompt action taken by numerous stakeholders.4 There was an independent inquiry into the underlying causes of the contamination, outbreak, and recall; the branch of the CFIA responsible for food safety was re-assigned to report to the Health Minister instead of the Agricultural Minister5; and new labelling laws were proposed and introduced for mechanically tenderized beef.6

XL Foods was a truly massive recall, but in the history of illness outbreaks, it was not the largest, not the deadliest, indeed it was somewhat unremarkable.7 Yet it was a political firestorm.

Fast-forward a few years: Over the course of 2015-2017 there were three outbreaks of salmonella attributed to frozen breaded chicken products in Canada.8 In total there were 86 reported illnesses across Canada including one death. Most recently, Public Health Agency of Canada has announced that there had been an outbreak between May 2017 and February 2018, with 30 illnesses, all attributed to frozen breaded chicken products.9

It was industry that took action first, and in 2015 voluntarily established guidelines for more prominent “uncooked” labels and clear directives not to microwave products.10

The CFIA and Health Canada, responding to the 2015 outbreak and recall, the largest of the three, spelled out steps consumers can take to avoid illness:

If you are preparing frozen raw breaded chicken products there are precautions you should take to protect your health.

  • Wash your hands thoroughly with soap and warm water before and after handling raw poultry products.
  • Use a separate plate, cutting board, and utensils when handling raw poultry products to prevent the spread of harmful bacteria.
  • Frozen raw breaded chicken products may appear to be pre-cooked or browned, but some contain raw chicken and should be handled and prepared no differently than raw poultry products.
  • Do not eat raw or undercooked poultry products. Cook all frozen, stuffed, breaded or raw poultry products to an internal temperature of at least 74°C (165°F) to ensure they are safe to eat. Whole poultry should be cooked to an internal temperature of 82°C (180°F).
  • Due to uneven heating, microwave cooking of frozen raw breaded poultry products including chicken nuggets, strips or burgers is not recommended. Always follow package cooking instructions, including products labelled Uncooked, Cook and Serve, Ready to Cook, and Oven Ready.

The federal agencies went on to attempt to describe what the government planned to do to prevent consumers from getting sick:

What the Government of Canada is doing
The Government of Canada is committed to food safety. The Public Health Agency of Canada is leading the human health investigation of this outbreak and is in regular contact with its federal and provincial partners to monitor and take collaborative steps to address the outbreak. Health Canada provides food-related health risk assessments to determine if the presence of a certain substance or microorganism poses a health risk to consumers. The Canadian Food Inspection Agency (CFIA) conducts food safety investigations into the possible food source of an outbreak. The Government of Canada will continue to update Canadians as new information related to this investigation becomes available.

Similar responses from the government accompanied the two 2017 illness outbreaks. By the end of 2017, after one death and over 80 illnesses, no federal agency had made a clear statement about what they would do to prevent consumers from getting sick, and other than industry’s voluntary guidelines, no changes to labelling or pathogen controls were introduced.

The relatively trite and useless response, from all stakeholders, including government, media, and the public stands in stark contrast to that which quickly followed the XL Foods scandal.11 Nobody is calling for the current Public Health minister Jane Philpott’s head, nobody is publicly calling out industry, the media has published public notices without the additional commentary and editorializing that took place in 2012, and politicians have yet to play the political blame-game. In fact, in the case of the June 2017 outbreak, the CFIA dragged its feet in going public with the name of the affected products and implicated company.

While industry’s voluntary labelling efforts are better than nothing, they don’t address the underlying problem because you can’t label pathogens out of your food. Labels, and the oft-repeated advice from government to properly cook these products severely misses the point. It places the burden of food safety solely on consumers’ shoulders. The assumption that contaminated products are safe if they are cooked properly also misses issues of cross-contamination and assumes that consumers have the right skills and tools (e.g., an accurate, quick-read thermometer) to actually determine when something is “cooked properly”. Or as Doug Powell on Barf Blog put it: “why is a teenager popping a few chicken nuggets in the microwave after school the critical control point in the frozen chicken thingie food safety system?” It also assumes that small, back-of-package labels that indicate the un-cooked state of the product are sufficient. The producers of frozen breaded chicken products had really been given a pass.

Things, thankfully, are changing, although it has taken three years and over 80 illnesses to get to government to act. On March 13, 2018, the CFIA announced it is working with the poultry industry to reduce the risk from Salmonella in frozen, breaded chicken products. From the press release:

These new measures call for processors to identify salmonella as a hazard and to implement changes in order to produce an end product that reduces salmonella to below a detectable amount. The CFIA has granted industry a 12-month implementation period, to begin immediately, to make these changes.

If only the feds had, you know, thought to ask “WTF is salmonella doing in frozen chicken thingies that people cook in the microwave?” as Doug Powell of Barf Blog did eight years ago.

I remain curious about why XL Foods was a national scandal and why it took so long for Salmonella in frozen breaded chicken to register even a modest public and regulatory response. I’ll be interested to see how this goes over the next 12 months.

  1. See, for example: here, and here, and here

  2. http://www.cbc.ca/news/canada/calgary/staff-at-xl-foods-shocked-by-layoffs-1.1269303 

  3. To whom the CFIA reported at the time. 

  4. Of course, how effective that action has been could be debated. 

  5. a move seen by some opposition members of parliament as a punishment of Gary Ritz by diminishing his responsibility and portfolio 

  6. Mechanically tenderized beef—cuts of beef that are tenderized by being punctured by dozens of small blades—was identified as one of the most problematic products. While pathogens on the surface of whole muscle (“intact”) cuts is permissible, because it will be in direct contact with the cooking surface and thus inactivated, the blades used in mechanical tenderization can inject pathogens from the surface of the muscle to the interior of the meat where they can grow, and where they may not be inactivated by heat without thorough cooking. Mechanically tenderized meat therefore can be higher risk than truly intact cuts, but prior to 2012, where existed no regulatory nor labelling distinction, despite the well-established difference in risk. 

  7. Some Which is by no means to diminish how terrible any foodborne illness outbreaks are. 18 illnesses is 18 illnesses too many. 

  8. In 2015 there was a recall of No Name and Compliments brand frozen, breaded chicken products linked to an outbreak of 51 illnesses in four eastern provinces. In July 2017, there was a recall of President’s Choice brand frozen, breaded chicken products link to an outbreak of 13 illnesses across Canada. And finally, in October 2017, there was a recall of Janes Pub Style frozen breaded chicken products linked to an outbreak of 22 illnesses (including 1 death) across Canada. 

  9. It’s not abundantly clear where one 2017 outbreak ends and the other begins. 

  10. Though I have not found the actual text or documentation for these guidelines, so it is not clear what the actual requirements are, whether implementation of these guidelines is voluntary or not, and what the incentives to follow/punishments for not following them are. 

  11. Lets also not forget about the 2008 listeriosis outbreak that killed 20 and nearly sank Maple Leaf foods, which was also followed by massive public and political outcry as well as an independent investigation


I am delighted to announce that as of March, I am a postdoctoral fellow in the Department of Geography and Planning at the University of Toronto.

I will be working with Virginia Maclaren on issues related to the role of the consumer under extended producer responsibility in Ontario.


Recently, I’ve been increasing the amount of information I keep in Apple’s Notes application, which received a significant feature update in iOS 9. One thing I struggled with, however, was finding an easy way to surface needed information in Notes at a specific time.

I don’t like to store reference data in my task manager or calendar because the information in those applications is, by definition, transient, and a lot of the material I need to reference to perform those tasks is not. DEVONthink (and DEVONthink To Go), my information manager of choice, very easily creates links to anything you store inside of it. You can paste these links anywhere on iOS and macOS and they will be clickable and take you directly to the desired file. Very handy. I frequently paste these links into OmniFocus tasks or calendar events so I don’t have to manually browse for the information by digging through countless folders.

But with more and more of my information going into Notes instead of DEVONthink, and as an adherent to the mantra that you never keep your reference materials in your task manager, I was a bit frustrated that I had to manually browse the Notes app to surface information I might need for a given task.

As it turns out, you can exploit the collaboration features introduced to Notes in iOS 9 to mimic DEVONthink’s file linking.

Open the note you would like to link to and tap the Add Person icon.

Add Person to Notes

You’ll be provided with a standard iOS share sheet. Locate the “Copy Link” action in the bottom row and tap it.

At this point you’re prompted to enter an email address or phone number of a potential collaborators.1 Since we’re collaborating with ourselves today, you can just tap Copy Link in the top right to copy the link to your clipboard and dismiss the share sheet.

Copy Link

Now you can easily paste this link into the Notes section of your task manager of choice, or the URL section of a calendar event, or anywhere else you please. Clicking on this link will take you straight to the note so you don’t have to do any time-consuming digging and browsing.

If you are interested in taking things a few steps further, Federico Viticci at MacStories has a great writeup on how he uses Notes on iOS.

  1. Which seems odd, since this is the same prompt you get if you select to share by Messages or Email, so I don’t see why you should also see it when using the generic Copy Link action! 


I’ve created a workflow to easily rename PDF files on iOS in the following pattern:

author year - title.pdf

Download the Workflow application for iOS

Download my PDF Namer workflow

The Basics

I love reading academic journal articles on my iPad. But what about finding those articles and saving the PDFs of those articles in an appropriate location with a nice title? Often, while reading, I’ll come across a citation to an article I want to track down. Ideally I’d be able to download and rename the file without switching devices (because lets face it, if I don’t do it right this moment, I’ll never do it). This can be somewhat cumbersome on an iPad, particularly if you don’t have your external keyboard deployed. It can be tricky to rename a file, while viewing that file, in iOS. You got to keep, in your frail human working memory, all the details such as the title and authors that you want to include in the name.

This was the perfect sort of problem to be solved by the very powerful iOS app called Workflow. If you aren’t familiar with Workflow on iOS, Workflow allows users to automate certain repetitive or complex tasks using a straightforward drag-and-drop interface ( it’s a bit like Automator on the Mac).

For a primer on Workflow, check out the great guide put together at iMore, and all the detailed articles put together by Federico Viticci at MacStories

I’ve written a workflow that walks users through the process of renaming a file stored in iCloud Drive, Dropbox, or Box. Once you install the Workflow app (if you don’t already have it installed), you can simply download my workflow to include it in your collection.

Once installed, all you have to do is run the workflow (either from the Today widget, or from within the Workflow application itself) and you’ll be walked through the process of renaming the file. Here’s how that should go:

  1. A dialogue box will prompt you to select the PDF you want from iCloud Drive
  2. Once selected, it will show you a preview of the PDF and ask you to copy the title to the clipboard and click “Done” in the top right.
  3. You’ll be presented with a text box containing the clipboard contents for you to confirm that you’ve got the title right.
  4. You’ll be shown a preview of the PDF again and prompted to copy the publication year to the clipboard and tap “done”. If there’s no easily-copiable year, you can just click Done and manually enter it in the next step.
  5. You’ll once again be presented with a text box filled with the date you selected. This allows you to confirm the correct year was selected, or manually enter the year if you didn’t copy it to the clipboard in step 4.
  6. You’ll be prompted to indicate the number of authors on the article.
  7. For each author (up to 3) you’ll go through the preview-copy-done routine we’ve been through several times. If you indicate that the article has more than three authors, the workflow will only ask you for the first author’s name and append “et al”. If there are superscript numerals attached to authors’ names, don’t worry about trying to avoid copying those, the workflow will remove them for you.
  8. You’ll be presented with a text box containing all the author names for you to verify or enter manually.
  9. Workflow will then delete the original file and prompt you to chose a location to save the newly renamed file.

Typically what I’ll do is save the PDF from Safari to the root directory (the bare /iCloud Drive or /Dropbox folder) and run the workflow from the Today widget. At the end of the workflow, I save the renamed file in the proper location (whichever folder matches the subject matter of the article) and the workflow simply deletes un-named file in the root directory.

Nitty Gritty

This seems like a lot of steps, but the beauty of this workflow is that you can go through the renaming process without having to touch the keyboard at all.

Now for some more technical details for those who want to get into the weeds a bit.

For such a simple task – renaming a file and saving it back to the location you choose – it looks somewhat complicated. I’ve included a number of conditionals and error-correcting steps to try account for as many contingencies as possible.

  1. Not all PDFs contain selectable text, or the actual text layer is corrupt. This is why you are prompted to verify the clipboard contents: if there are errors this allows you to correct it, if the text isn’t selectable, you can enter it manually. This adds an extra step to each part of the title, but it means that this workflow is still usable in most cases even for old or edge-case PDFs.
  2. I deal with strange capitalizations and strip special or unwanted characters like trailing spaces at each step. In many cases, journals will use superscript numerals to denote author affiliations. I’ve noticed that using the iOS text selectors, it can be hard to avoid these numerals, so in the Author selection stage, I use a REGEX query to find numerals and delete them. A similar issue exists with journal titles that span across a line. In some cases, this may actually result in a line-break being copied to the clipboard. Again, a REGEX query finds these and replaces them with a space.
  3. If there are more than three authors, I want to append an “et al” rather than have the user copy and paste a dozen author names. This required me to set up several nested conditionals for one author, between 1 and 4 authors, and 4 or more authors.

Customizations

My default the workflow prompts you to select a file from iCloud Drive, which is my file sync of choice. If you want to have it prompt you to select files from Dropbox or Box, simply make the appropriate change to “Get File” and “Save File” actions at the beginning and end of the Workflow.

Unfortunately, if you would like to have the filename contain different information or ordered differently, that would require some extensive reconfiguring of the workflow. Feel free to dive in to try and do it yourself if you’d like, or reach out to me and I’ll see what I can do.

Reach out to me on Twitter and let me know how it goes!


I have a new publication with Myra Hird in the Journal of Crime, Law, and Social Change titled “Food Security and Secure Food in the Anthropocene”

Modes of Governing Abstract