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.