Run Grouper iOS Project

I went with a project-based approach to learning Swift and this is (a start to) what I came up with. I used to run with a group in Brooklyn and I would miss the meetups a lot because I would forget the meetup times and show up too early or too late. The running world is very punctual and this app targets runners who aren’t.

This project made me realize that I mostly build toy apps with extremely limited functionality. Besides my website I don’t have much experience with building for the long run, so this app turned out to be a challenge. I started by mocking it up in invision, a neat web tool you can use to prototype mobile apps. I learned about invision from a lecture about ‘UX’ at General Assembly in New York. UX, as far as I can tell, stands for User Experience and is a play on ‘UI’ (User Interface). The app market is saturated and at the slightest inconvenience many users will move on to the next one. You’re also not allowed to just push whatever garbage you came up with out to the iOS app store, so you have to make sure you follow proper design principles. UX is important because nobody has the time nor the patience to read instruction manuals anymore. So, software designers have to make functionality apparent based on imagery, placement, motion, color schemes, and dozens of other options. The idea is you want everything streamlined so the path of least resistance leads to favorable results or ‘goal conversions’ as Google Analytics calls it. For this app the ‘goal’ is to inform our users about the local meetups as easily as possible. Here’s the rough draft of the Run Grouper app, with zero functionality: Tada! Looks nice, right? Now for the hard part.

Apple computers come with Xcode, an IDE for developing native iOS apps. It has a photoshop-esque complexity and can be overwhelming with the number of features. The ‘storyboard’ mode has some unique drag and drop functionality where you link storyboard components like buttons, textfields, etc. by literally dragging them into your code. You can then manipulate the generated “IBOutlet variable” through your code.

Swift is very strongly-typed and complains a lot when you don’t follow the rules. It is definitely tricky transitioning from languages like Ruby, Python, JavaScript, etc. to Swift, but it should payoff in the long run. One common issue of weakly typed languages is passing the wrong variable type into a function causing unpredicted behavior. Swift should catch that kind of problem before compiling and generate more maintainable code.

I used this tutorial from Apple to get the app into its basic state. That gives our basic UI which consists of a list of ‘food items’ (soon to be run items) and functionality to add, delete, edit, and persist data. The next thing I did was replace the food-related stuff with run-related stuff and get rid of the rating system.  Although that might be a cool addition in the future.

We don’t want to force the user to enter their own runs, but we want the runs to load in based on their established group. The first step of the project is loading data from a website into the app. I opted to use the JSON format over XML to save myself some typing and because websites like this and this host JSON data freely. Here’s the Swift code to load in the JSON data, please pardon the weird syntax highlighting:

private func loadSampleRuns(){
        let url = URL(string: "mywebsite.com/run.json")
        URLSession.shared.dataTask(with:url!, completionHandler:
            {(data, response, error) in
                guard let data = data, error == nil else { return }
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any]
                    if let runs_load = json["runs"] as? [[String: AnyObject]] {
                        for run in runs_load {
                            
                            let r_name = self.loadJSONString(jsonObject: run, name: "runName")
                            //Make sure that the run name is not already loaded
                            if !self.checkForRunName(name: r_name) {
                                let r_day = self.loadJSONString(jsonObject: run, name: "runDay")
                                let r_time = self.loadJSONString(jsonObject: run, name: "runTime")
                                let r_location = self.loadJSONString(jsonObject: run, name: "runLocation")
                                guard let current_run = Run(name:r_name, day: r_day, time: r_time, location: r_location) else {
                                    fatalError("Unable to instantiate run")
                                }
                                self.runs += [current_run]
                                self.tableView.reloadData()
                            }
                        }
                    }
                } catch let error as NSError {
                    print(error)
                }
        }).resume()
    }
    
    private func loadJSONString(jsonObject: [String:AnyObject], name: String) -> String {
        return(jsonObject[name] as? String! ?? "EMPTY")
    }
    private func checkForRunName(name: String) -> Bool {
        for run in runs {
            if run.name == name {
                return true
            }
        }
        return false
    }

Not the most attractive code, but it works well enough. Next we need  consistent, formatted time and date information. We mainly need the difference in time, so we can tell the reader that the meetup is in 3 hours, tomorrow, or next week. I found this helpful thread on stackexchange for differencing JSON formatted strings with Swift and here’s the code:

private func getDateFromString(_ dateString: String?) -> Date? {
        guard let dateString = dateString else {
            return nil
        }
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
        var date: Date?
        date = dateFormatter.date(from: dateString)
        if date == nil {
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZ"
            date = dateFormatter.date(from: dateString)
        }
        return date
    }
    private func compareDates(date1 : Date, date2 : Date) -> DateComponents {
        let calendar = Calendar.current
        return calendar.dateComponents([.day, .hour, .minute], from: date1, to: date2)
    }

I’m still learning how to meet the  app store guidelines and debating whether it’s worth it to pay the 99$/year fee to deploy this onto the app store. I think the concept is novel enough, but this will not meet the stylistic guidelines yet. You can look at the github repository here, notice it’s still named “foodtracker” after the iOS tutorial.

Folder Action to Copy Dropbox Screenshots to a Different Directory using the built-in OSX Automator

For mac users who take a lot of Screenshots and also happen to have Dropbox, you may have noticed that the screenshots are automatically saved to the Dropbox/Screenshots subdirectory. Annoyingly, there is no way to change this destination within your dropbox preferences. My solution Here is a guide to setup the OS X Automator to take care of this for you, well, automatically.

The OSX Automator is a macro builder that comes standard with Apple computers. Macros are automated functions that can take care of many mindless repetitive tasks after being set up. I just found out about it recently when I came across this Dropbox issue and I want to share how I set it up.

Right click on the folder and select Services > Folder Actions Setup…

 

A modal box titled “Folder Actions Setup” should appear. Select “move – source to dest files.scpt” then click Attach.

The name of the folder should appear on the Folders With Actions list. Make sure it is checked. Now we are capable of adding macros to the folder using the Automator.

Open up Automator from Spotlight Search or the Applications folder.

Select New Workflow then click on Files & Folders from the left navigation menu

You should see an empty workspace with the message “Drag actions or files here to build your workflow.” The first thing to do is get the specified finder items, so find Get Specified Finder Items from the searchable action list and drag it over.

Click the Add button to find the folder from earlier.

Next, we want to duplicate the file so we don’t lose the original.

Drag that below the Get Specified… item in your workflow. Lastly, we want to move the duplicated file to the new directory. You can do this by using the Move Finder Items action.

Drag this below the Duplicate Finder Items action.

Select where you are moving the files from the dropdown “To” selector.

Your total workflow should look something like this:

Go ahead and click the Run button at the top and test it out by moving taking a few screenshots.

Hope this helped!