Categories
Uncategorized

Always keep an eye on your throughput

this device has low io throughput

Categories
Uncategorized

How to build an iOS weather app in Swift, part 3: Giving the app a user interface

weather

The story so far

In the first article in this series, we introduced the OpenWeatherMap service and showed you how to send it a request for the current weather in a specified city and get a JSON response back, first manually, then programmatically. We expanded on this in the second article, where we gave the app the ability to convert that JSON into a dictionary from which Swift can easily extract data.

In both articles, the weather information wasn’t being presented in the user interface. All output was printed out to the debug console. It’s now time to make our app display information to the user. By the end of this article, we want our app to look like this:

tampa forecast

Let’s get started!

The WeatherGetter class

So far, we’ve spent most of our time working on the WeatherGetter class, which connects to OpenWeatherMap.org, provides it with a city name, and retrieves the current weather for that city.

WeatherGetter makes use of:

  • the Shared Session instance of the NSURLSession class (which provides an API for sending and receiving data to and from a given URL), and
  • an instance of NSURLSessionDataTask, which downloads data from a specified URL into memory.

Here’s how these classes relate to each other:

The user interface lives in the view controller, while the weather networking apparatus lives in WeatherGetter. Here’s the setup we’re aiming for:

view controller - weathergetter relationship 1

We create this setup using delegation, a design pattern that you’ll often see in iOS programming when one object coordinates its activities with another object:

view controller - weathergetter relationship 2

We’ll add a protocol definition to WeatherGetter:

protocol WeatherGetterDelegate {
  func didGetWeather(weather: Weather)
  func didNotGetWeather(error: NSError)
}

This definition specifies the interface for two methods:

  • didGetWeather, which we’ll call if we were able to retrieve the weather data from OpenWeatherMap and parse its contents. It will provide the weather data in the form of a Weather struct, which we’ll talk about in a moment.
  • didNotGetWeather, which we’ll call if we were not able to retrieve the weather data from OpenWeatherMap or if we weren’t able parse its contents. It will provide an error object that will explain what kind of error occurred.

We’ll have the view controller register itself with WeatherGetter, and both didGetWeather and didNotGetWeather will be implemented in the view controller.

Here’s the complete WeatherGetterDelegate.swift file:

import Foundation


// MARK: WeatherGetterDelegate
// ===========================
// WeatherGetter should be used by a class or struct, and that class or struct
// should adopt this protocol and register itself as the delegate.
// The delegate's didGetWeather method is called if the weather data was
// acquired from OpenWeatherMap.org and successfully converted from JSON into
// a Swift dictionary.
// The delegate's didNotGetWeather method is called if either:
// - The weather was not acquired from OpenWeatherMap.org, or
// - The received weather data could not be converted from JSON into a dictionary.
protocol WeatherGetterDelegate {
  func didGetWeather(weather: Weather)
  func didNotGetWeather(error: NSError)
}


// MARK: WeatherGetter
// ===================

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "YOUR API CODE HERE"
 
  private var delegate: WeatherGetterDelegate
  
  
  // MARK: -
  
  init(delegate: WeatherGetterDelegate) {
    self.delegate = delegate
  }
  
  func getWeatherByCity(city: String) {
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    getWeather(weatherRequestURL)
  }
  
  private func getWeather(weatherRequestURL: NSURL) {
    
    // This is a pretty simple networking task, so the shared session will do.
    let session = NSURLSession.sharedSession()
    
    // The data task retrieves the data.
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      if let networkError = error {
        // Case 1: Error
        // An error occurred while trying to get data from the server.
        self.delegate.didNotGetWeather(networkError)
      }
      else {
        // Case 2: Success
        // We got data from the server!
        do {
          // Try to convert that data into a Swift dictionary
          let weatherData = try NSJSONSerialization.JSONObjectWithData(
            data!,
            options: .MutableContainers) as! [String: AnyObject]

          // If we made it to this point, we've successfully converted the
          // JSON-formatted weather data into a Swift dictionary.
          // Let's now used that dictionary to initialize a Weather struct.
          let weather = Weather(weatherData: weatherData)
          
          // Now that we have the Weather struct, let's notify the view controller,
          // which will use it to display the weather to the user.
          self.delegate.didGetWeather(weather)
        }
        catch let jsonError as NSError {
          // An error occurred while trying to convert the data into a Swift dictionary.
          self.delegate.didNotGetWeather(jsonError)
        }
      }
    }
    
    // The data task is set up...launch it!
    dataTask.resume()
  }
  
}

The Weather struct

In order to more easily send the weather data from WeatherGetter to the view controller, I created a struct called Weather. It lives in a file called Weather.swift, and it’s shown in its entirety below:

import Foundation

struct Weather {
  
  let dateAndTime: NSDate
  
  let city: String
  let country: String
  let longitude: Double
  let latitude: Double
  
  let weatherID: Int
  let mainWeather: String
  let weatherDescription: String
  let weatherIconID: String
  
  // OpenWeatherMap reports temperature in Kelvin,
  // which is why we provide celsius and fahrenheit
  // computed properties.
  private let temp: Double
  var tempCelsius: Double {
    get {
      return temp - 273.15
    }
  }
  var tempFahrenheit: Double {
    get {
      return (temp - 273.15) * 1.8 + 32
    }
  }
  let humidity: Int
  let pressure: Int
  let cloudCover: Int
  let windSpeed: Double
  
  // These properties are optionals because OpenWeatherMap doesn't provide:
  // - a value for wind direction when the wind speed is negligible 
  // - rain info when there is no rainfall
  let windDirection: Double?
  let rainfallInLast3Hours: Double?
  
  let sunrise: NSDate
  let sunset: NSDate
  
  init(weatherData: [String: AnyObject]) {
    dateAndTime = NSDate(timeIntervalSince1970: weatherData["dt"] as! NSTimeInterval)
    city = weatherData["name"] as! String
    
    let coordDict = weatherData["coord"] as! [String: AnyObject]
    longitude = coordDict["lon"] as! Double
    latitude = coordDict["lat"] as! Double
    
    let weatherDict = weatherData["weather"]![0] as! [String: AnyObject]
    weatherID = weatherDict["id"] as! Int
    mainWeather = weatherDict["main"] as! String
    weatherDescription = weatherDict["description"] as! String
    weatherIconID = weatherDict["icon"] as! String
    
    let mainDict = weatherData["main"] as! [String: AnyObject]
    temp = mainDict["temp"] as! Double
    humidity = mainDict["humidity"] as! Int
    pressure = mainDict["pressure"] as! Int
    
    cloudCover = weatherData["clouds"]!["all"] as! Int
    
    let windDict = weatherData["wind"] as! [String: AnyObject]
    windSpeed = windDict["speed"] as! Double
    windDirection = windDict["deg"] as? Double
    
    if weatherData["rain"] != nil {
      let rainDict = weatherData["rain"] as! [String: AnyObject]
      rainfallInLast3Hours = rainDict["3h"] as? Double
    }
    else {
      rainfallInLast3Hours = nil
    }
    
    let sysDict = weatherData["sys"] as! [String: AnyObject]
    country = sysDict["country"] as! String
    sunrise = NSDate(timeIntervalSince1970: sysDict["sunrise"] as! NSTimeInterval)
    sunset = NSDate(timeIntervalSince1970:sysDict["sunset"] as! NSTimeInterval)
  }
  
}

When initializing the Weather struct, you pass it the [String: AnyObject] dictionary created by parsing the JSON from OpenWeatherMap. Weather then takes that data from that dictionary and uses it to initialize its properties.

The Weather struct does a number of useful things:

  • It turns the dictionary-of-dictionaries structure of the data from OpenWeatherMap into a nice, flat set of weather properties,
  • it provides the date/time values provided by OpenWeatherMap in iOS’ native NSDate format rather than in Unix time (an integer specifying time in seconds after January 1, 1970), and
  • it uses computed properties to report temperatures in degrees Celsius and Fahrenheit rather than Kelvin.

You may notice that the windDirection and rainfallInLast3Hours properties are optionals. That’s because OpenWeatherMap doesn’t always provide those values. It reports a wind direction if and only if the wind speed is greater than zero, and it reports rain information if and only if it’s raining.

The view controller

Here are the controls I put on the view, along with the names of their outlets:

view controller layout

I also created a Touch Up Inside action for the Get weather for the city above button called getWeatherForCityButtonTapped.

Here’s the code for ViewController.swift:

import UIKit

class ViewController: UIViewController,
                      WeatherGetterDelegate,
                      UITextFieldDelegate
{
  @IBOutlet weak var cityLabel: UILabel!
  @IBOutlet weak var weatherLabel: UILabel!
  @IBOutlet weak var temperatureLabel: UILabel!
  @IBOutlet weak var cloudCoverLabel: UILabel!
  @IBOutlet weak var windLabel: UILabel!
  @IBOutlet weak var rainLabel: UILabel!
  @IBOutlet weak var humidityLabel: UILabel!
  @IBOutlet weak var cityTextField: UITextField!
  @IBOutlet weak var getCityWeatherButton: UIButton!
  
  var weather: WeatherGetter!
  
  
  // MARK: -
  
  override func viewDidLoad() {
    super.viewDidLoad()
    weather = WeatherGetter(delegate: self)
    
    // Initialize UI
    // -------------
    cityLabel.text = "simple weather"
    weatherLabel.text = ""
    temperatureLabel.text = ""
    cloudCoverLabel.text = ""
    windLabel.text = ""
    rainLabel.text = ""
    humidityLabel.text = ""
    cityTextField.text = ""
    cityTextField.placeholder = "Enter city name"
    cityTextField.delegate = self
    cityTextField.enablesReturnKeyAutomatically = true
    getCityWeatherButton.enabled = false
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }
  
  
  // MARK: - Button events
  // ---------------------
  
  @IBAction func getWeatherForCityButtonTapped(sender: UIButton) {
    guard let text = cityTextField.text where !text.isEmpty else {
      return
    }
    weather.getWeather(cityTextField.text!.urlEncoded)
  }
  
  
  // MARK: -
  
  // MARK: WeatherGetterDelegate methods
  // -----------------------------------
  
  func didGetWeather(weather: Weather) {
    // This method is called asynchronously, which means it won't execute in the main queue.
    // ALl UI code needs to execute in the main queue, which is why we're wrapping the code
    // that updates all the labels in a dispatch_async() call.
    dispatch_async(dispatch_get_main_queue()) {
      self.cityLabel.text = weather.city
      self.weatherLabel.text = weather.weatherDescription
      self.temperatureLabel.text = "\(Int(round(weather.tempCelsius)))°"
      self.cloudCoverLabel.text = "\(weather.cloudCover)%"
      self.windLabel.text = "\(weather.windSpeed) m/s"
      
      if let rain = weather.rainfallInLast3Hours {
        self.rainLabel.text = "\(rain) mm"
      }
      else {
        self.rainLabel.text = "None"
      }
      
      self.humidityLabel.text = "\(weather.humidity)%"
    }
  }
  
  func didNotGetWeather(error: NSError) {
    // This method is called asynchronously, which means it won't execute in the main queue.
    // ALl UI code needs to execute in the main queue, which is why we're wrapping the call
    // to showSimpleAlert(title:message:) in a dispatch_async() call.
    dispatch_async(dispatch_get_main_queue()) {
      self.showSimpleAlert(title: "Can't get the weather",
                           message: "The weather service isn't responding.")
    }
    print("didNotGetWeather error: \(error)")
  }
  
  
  // MARK: - UITextFieldDelegate and related methods
  // -----------------------------------------------

  // Enable the "Get weather for the city above" button
  // if the city text field contains any text,
  // disable it otherwise.
  func textField(textField: UITextField,
                 shouldChangeCharactersInRange range: NSRange,
                 replacementString string: String) -> Bool {
    let currentText = textField.text ?? ""
    let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(
                            range,
                            withString: string)
    getCityWeatherButton.enabled = prospectiveText.characters.count > 0
    print("Count: \(prospectiveText.characters.count)")
    return true
  }
  
  // Pressing the clear button on the text field (the x-in-a-circle button
  // on the right side of the field)
  func textFieldShouldClear(textField: UITextField) -> Bool {
    // Even though pressing the clear button clears the text field,
    // this line is necessary. I'll explain in a later blog post.
    textField.text = ""
    
    getCityWeatherButton.enabled = false
    return true
  }
  
  // Pressing the return button on the keyboard should be like
  // pressing the "Get weather for the city above" button.
  func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    getWeatherForCityButtonTapped(getCityWeatherButton)
    return true
  }
  
  // Tapping on the view should dismiss the keyboard.
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    view.endEditing(true)
  }
  
  
  // MARK: - Utility methods
  // -----------------------

  func showSimpleAlert(title title: String, message: String) {
    let alert = UIAlertController(
      title: title,
      message: message,
      preferredStyle: .Alert
    )
    let okAction = UIAlertAction(
      title: "OK",
      style:  .Default,
      handler: nil
    )
    alert.addAction(okAction)
    presentViewController(
      alert,
      animated: true,
      completion: nil
    )
  }
  
}


extension String {
  
  // A handy method for %-encoding strings containing spaces and other
  // characters that need to be converted for use in URLs.
  var urlEncoded: String {
    return self.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLUserAllowedCharacterSet())!
  }
  
}

If you run the app, you should get results similar to this:

tampa forecast

st. johns forecast

If the temperatures seem a little chilly to you, it’s because you’re probably expecting them in Fahrenheit and I’m currently displaying them in Celsius. You can change this by changing the line in the didGetWeather method from

self.temperatureLabel.text = "\(Int(round(weather.tempCelsius)))°"

to

self.temperatureLabel.text = "\(Int(round(weather.tempFahrenheit)))°"

Take a good look at the code for the view controller — I’ve included a couple of tricks that I think improve the UI. I’ll explain them in greater detail in posts to follow.

web horizontal rule

In the next installment in this series, we’ll add geolocation capability to the app, so that the user can get the weather at his/her current location without having to type it in.

Download the project files

xcode download

You can download the project files for this article (51KB zipped) here.

Categories
Uncategorized

“Uncanny Valley”, and other articles by Anna Wiener that you should read

Uncanny Valley

ripstik

The RipStik gets a mention in Anna Wiener’s Uncanny Valley.

If you’re looking for some smart Sunday reading about the oft-neglected people aspect of technology, start with Anna Wiener’s Uncanny Valley, where she writes about her experience working that alternate reality known as San Francisco, circa 2013.

It’s a world where your culture and the company culture are one and the same, job descriptions are an energy-drink admixture of “HR’s idea of fun and a 23-year-old’s idea of work-life balance”, you can’t tell whether getting together over drinks is dating or networking, and FOMO hangs in the air like the city’s famous summer fog.

accept - tentative - decline

Here’s an excerpt:

A MEETING IS DROPPED MYSTERIOUSLY onto our calendars, and at the designated time we shuffle warily into a conference room. The last time this happened, we were given forms that asked us to rate various values on a scale of 1 to 5: our desire to lead a team; the importance of work-life balance. I gave both things a 4 and was told I didn’t want it enough.

The conference room has a million-dollar view of downtown San Francisco, but we keep the shades down. Across the street, a bucket drummer bangs out an irregular heartbeat. We sit in a row, backs to the window, laptops open. I look around the room and feel a wave of affection for these men, this small group of misfits who are the only people who understand this new backbone to my life. On the other side of the table, our manager paces back and forth, but he’s smiling. He asks us to write down the names of the five smartest people we know, and we dutifully oblige. I look at the list and think about how much I miss my friends back home, how bad I’ve been at returning phone calls and emails, how bloated I’ve become with start-up self-importance, how I’ve stopped making time for what I once held dear. I can feel blood rush to my cheeks.

“OK,” my manager says. “Now tell me: why don’t they work here?”

snow-crash-cover

Another excerpt reminds me of the line from Neal Stephenson’s Snow Crash that’s stayed with me since first reading it in 1992: “It was, of course, nothing more than sexism, the especially virulent type espoused by male techies who sincerely believe that they are too smart to be sexists”:

WE HIRE AN ENGINEER fresh out of a top undergraduate program. She walks confidently into the office, springy and enthusiastic. We’ve all been looking forward to having a woman on our engineering team. It’s a big moment for us. Her onboarding buddy brings her around to make introductions, and as they approach our corner, my coworker leans over and cups his hand around my ear: as though we are colluding, as though we are 5 years old. “I feel sorry,” he says, his breath moist against my neck. “Everyone’s going to hit on her.”

I include this anecdote in an email to my mom. The annual-review cycle is nigh, and I’m on the fence about whether or not to bring up the running list of casual hostilities toward women that add unsolicited spice to the workplace. I tell her about the colleague with the smart-watch app that’s just an animated GIF of a woman’s breasts bouncing in perpetuity; I tell her about the comments I’ve fielded about my weight, my lips, my clothing, my sex life; I tell her that the first woman engineer is also the only engineer without SSH access to the servers. I tell her that compared with other women I’ve met here, I have it good, but the bar is low. It’s tricky: I like these coworkers — and I dish it back — but in the parlance of our industry, this behavior is scalable. I don’t have any horror stories yet; I’d prefer things stay this way. I expect my mother to respond with words of support and encouragement. I expect her to say, “Yes! You are the change this industry needs.” She emails me back almost immediately. “Don’t put complaints about sexism in writing,” she writes. “Unless, of course, you have a lawyer at the ready.”

web horizontal rule

Go and read Uncanny Valley. If my endorsement isn’t enough, take Paul “ftrain” Ford’s

…or Leigh Alexander, from whom I found out about the article in the first place:

Inside Silicon Valley’s Big Pitch Day

y combinator demo day

Inside Silicon Valley’s Big Pitch Day is another piece by Anna Wiener about Y Combinator’s Demo Day, the show-and-tell held at the end of every March and August where companies funded by the seed accelerator firm parade their people and products to a room of very exclusive guests. She covers it not with the breathless tech-for-tech’s sake style that a more nerd-focused writer might employ, nor with the tech-for-getting-filthy-rich’s sake manner that someone with a Forbes-y/Business Insider-y bent would use. As she puts it:

I’ve worked in tech for a few years and love technology, if not always the techindustry: I’m neither an entrepreneur nor an engineer, I’m just a humanist with a sociology degree. I went to Demo Day as both an insider and an outsider, hoping to see a slice of the future.

She points out the sili-ness that comes with Silicon Valley, but at the end, she also points out reasons to be hopeful:

Still, to see one of Silicon Valley’s most lauded accelerators give a platform to people working in sanitation, social services, and healthcare felt tremendous. The future of technology is not necessarily in consumer or B2B software, and it’s not necessarily in the United States, either—some of the more compelling ideas came from international companies catering to local markets. These were hardly the most glamorous companies, but they were the companies that seemed most important to get in front of an audience with as much economic, cultural, and political clout as those assembled at Demo Day.

The Y Combinator motto is straightforward: “Make something people want.” At Demo Day, there were signs that the accelerator, and Silicon Valley as a whole, could also help companies make something people need.

For those of you who were curious about the company she was writing about in Uncanny Valley, she drops a hint in Inside Silicon Valley’s Big Pitch Day: “Full disclosure: From 2013-2014, I worked for a Y Combinator startup. The CEO was 24.”

Hacking Technology’s Boy’s Club

ellen ullman

There are a precious few books that I have to keep buying over and over again because I keep giving away my copy to friends. One of these books is Close to the Machine: Technophilia and its Discontents by Ellen Ullman. Published in 1997, it’s a memoir that tells a good number of interesting stories, shows the human side of computer (and yes, there is one), and finally, it manages to pull off what should be impossible — it makes coding look interesting, and even exciting, to non-coders. As a reader of this blog, it may seem strange to you to not think of programming as exciting, but we’re the kind of people who look forward to a long flight as a chance to try out a new programming language or API.

Anna Wiener’s January 2016 article for The New Republic, Hacking Technology’s Boy’s Club, is a good intro to Ellen Ullman for those of you who aren’t familiar with her or her work. In some parts, we see an Ullman who’s concerned about the social issues of the tech industry and its products:

“It will not work to keep asking men to change,” Ullman told me. “Many have no real objective to do so. There’s no reward for them. Why should they change? They’re doing well inside the halls of coding.” To be perfectly clear: Ullman isn’t anti-geek-culture; she’s not anti-technology; she’s not anti-men. She doesn’t want to raze the clubhouse. She simply wants those inside to open the door.

Though she retired from the tech industry at the end of the last boom, to read Ullman’s work is to remember she’s been with us all along. Code, for all its elegance and power, is just a tool. “As with all advances in technology, the new offerings are often helpful, and marvelous—sometimes frightening, as with advances in surveillance,” she said. “The services are enormously convenient, but then there is the culture left behind. When we receive the dry cleaning delivery, we no longer see who does the work. We don’t see the tailor in the window, the presser surrounded by steam. When you order food on your phone from GrubHub, you don’t see the cooks and helpers in the hot kitchen.” The question of who delivers to whom, she continued, is directly related to inequality at large—it’s essential that the technologies we create and use are also building a world we want to live in.

But she’s just as into the hardcore, technical nitty-gritty that you and I love:

One afternoon last summer, I invited Ullman to my workplace. Within minutes, she and two young engineers were debating the merits of strongly typed languages, a conversation they’ve had many times before; it quickly became clear that Ullman had tipped the scales. “See? I told you so,” one said, vindicated. As enjoyable as it was to watch her, I was in over my head. Ullman noticed immediately. “Sorry—you must be bored,” she said. “This is fun for me.”

web horizontal rule

There you have it: three interesting articles by Anna Wiener. I look forward to reading more of her work.

Categories
Tampa Bay

Tampa iOS Meetup, Wednesday, May 4th: Adding geolocation to our weather app

adding geolocation to our weather app

The next Tampa iOS Meetup has been announced for Wednesday, May 4th at 6:30 p.m.. This one builds on the previous meetup (but if you missed the last one, we’ll get you caught up) and is called Adding geolocation to our weather app.  It takes place at our usual spot: Energy Sense Finance, 3825 Henderson Boulevard (just west of Dale Mabry), Suite 300.

We’ll pick up from where our last meetup, Build a simple weather app (and learn basic network programming along the way), left off. The app required you to specify your location before it would report the weather. But that’s not how most weather apps work: they use geolocation to get your phone’s coordinates and provide them to the weather service. In this meetup, we’ll show you how to harness the power of iOS geolocation through the Core Location framework.

We’ll begin with a quick walkthrough of last meetup’s weather app, just as a reminder for those of you who were there, and as a way for those of you who weren’t to get caught up. Then we’ll get right into the business of adding geolocation capability to our weather app, so that when you run it, it displays the weather for your location.

At the end of this session, you’ll know how to make use of geolocation through Core Location. We’ll make the source code and presentation materials available at the end, to make it easier for you to start your own geolocation projects.

Join us this Wednesday, get to know your fellow Tampa Bay iOS developers, and get ready to learn and have some fun!

Tampa iOS Meetup is a monthly meetup run by local mobile developer/designer Angela Don and Yours Truly. While Tampa has a couple of great iOS developer meetups — Craig Clayton’s Suncoast iOS and Chris Woodard’s Tampa Bay Cocoaheads, we figured that there was room for a third iOS meetup in the Tampa Bay area, and especially one that would stray into other areas of mobile development. So we made one.

The Details

  • What: Tampa iOS’ Meetup’s “Adding geolocation to our weather app” session. Please sign up on our Meetup page so we can plan accordingly!
  • When: Wednesday, May 4, 2016, 6:30 p.m. – 9:00 p.m. We’ll have some snacks at 6:30, with the presentation beginning at 7:00.
  • Where: Energy Sense Finance, 3825 Henderson Boulevard (just west of Dale Mabry), Suite 300. See the map below.
  • What to bring: Yourself, but if you’d like to follow along, bring your Macbook and make sure it’s got the latest Xcode.
  • What to read in advance: If you’re one of those people who likes to do some readings ahead of a presentation, try this example location app written in Swift. We’ll be using our own tutorial material, but this may come in handy.
Categories
Uncategorized

Want to test an iPhone/iPad game?

Wine Crush is a simple “Candy Crush”-like game that I’m developing for my friends at Aspirations Winery in the nearby city of Clearwater. It’s the first of a few apps that I hope to publish to the App Store this year. If you’ve got an hour or so to spare and are the sort of person who always has ideas on how apps could be improved, this opportunity’s the one you’ve been waiting for!

Wine Crush is a pretty straightforward game. You play by creating matching groups of wine-related images — glasses of wine, bottles, corks, grapes and cheese — in groups of three or more. You can create horizontal matching groups…

Animation showing how players form a horizontal match in Wine Crush.

…or vertical matching groups:

Animation showing how players form a vertical match in Wine Crush.

You score points for creating matching groups, and the goal for each level of the game is to score a target number of points within a given number of moves. If you meet the goal, you get to proceed to the next level. If you don’t, it’s GAME OVER.

Anitra Pavka (3rd from left) and Joey deVilla (4th from right) at 'Wine-O Bingo' at Aspirations Winery, Clearwater, Florida.

“Wine-O Bingo” at Aspirations Winery, summer 2014. My wife Anitra’s third from the left, and I’m the smiling guy across the table from her, fourth from the right.

Wine Crush is designed as a promotional tool for Aspirations Winery, which is run by Bill and Robin Linville. My wife Anitra has been buying their wines for years. She took me to one of their regular “Wine-O Bingo” events in 2014 (pictured above), and made friends with them after entertaining their guests with a couple of their accordion numbers. Soon after, I approached them with a proposition: Would you like to have an iPhone game for your winery?

They said yes, and I got to work. Starting with the “How to make a game link Candy Crush” tutorials from RayWenderlich.com as a basis, I put together a game with wine-related imagery and Aspirations’ branding, using artwork that Robin provided. The backgrounds for the games’ levels are various labels for Aspirations’ wines, and there are a couple of buttons on the main screen that you can click to find out more about the winery.

For Aspirations, it’s a cute little way to get their name out their in a way that sets them apart from most other small family-owned wineries. As for me, this app is a way for me to help out some friends, sharpen my programming skills, build a portfolio, and gain some valuable experience with the App Store. Aside from a fair bit of free wine (for which I’m very grateful), I’m not getting paid for this project.

The title screen and two game screens from Wine Crush.

Wine Crush needs to be tested before I put it on the App Store. As the developer, I’m a little too close to the project to spot all the bugs and places where it could be improved. That’s where you come in. I’m looking for a small group of people who like trying out new apps to take a pre-release version of Wine Crush for a spin, point out problems and crashes, and give me some feedback. I will also ask you to test some other interesting iPhone/iPad/Apple TV projects I’m working on. It won’t be an all-consuming process; all I’m asking for is a little bit of your downtime and your opinions.

Does the opportunity to test and give feedback on soon-to-be-released iPhone/iPad/Apple TV apps sound interesting to you? If it does and you’d like to join the test group, drop me a line at joey@joeydevilla.com.

This article also appears in The Adventures of Accordion Guy in the 21st Century.

Categories
Uncategorized

Tonight at the Tampa Bay UX Meetup: Apple TV UX with Anitra Pavka!

apple tv fun facts

A slide from tonight’s presentation.

tampa bay user experienceI was scheduled to speak tonight at the Tampa Bay UX Meetup and talk about Apple TV UX with my wife Anitra, but as luck would have it, work commitments this evening with GSG’s biggest client have conspired to keep me away.

The show must go on, and it’s in very good hand. Anitra’s forgotten more about user experience than I will ever learn, what with her work as a user experience and web accessibility consultant. She also wrote the accessibility chapter in O’Reilly’s HTML5 Cookbook and was a technical editor for O’Reilly’s Universal Design for Web Applications book. She’ll apply her knowledge to the fourth-generation Apple TV user experience, and walk you through it. There’ll be a presentation, followed by live demos of Apple TV apps and interface. She’ll also point you to resources that you can use to help you design and even build tvOS apps.

It all happens tonight at 6:00 p.m. at the offices of 352 Inc., located at 5100 West Kennedy Boulevard, Suite 352 in Tampa. You can sign up and find out more on the Tampa Bay UX Meetup page. My thanks to organizer Krissy Scoufis for all her help!

Categories
Uncategorized

Developer news roundup: Android, iOS/Swift, and regrets and mistakes

Android

new android emulator

Click the screenshot to see it at full size.

Regrets and mistakes, part one

nuclear explosion

This question appeared on Server Fault on Sunday:

I run a small hosting provider with more or less 1535 customers and I use Ansible to automate some operations to be run on all servers. Last night I accidentally ran, on all servers, a Bash script with a rm -rf {foo}/{bar} with those variables undefined due to a bug in the code above this line.

All servers got deleted and the offsite backups too because the remote storage was mounted just before by the same script (that is a backup maintenance script).

How I can recover from a rm -rf / now in a timely manner?

Most of the answers were along the lines of: “If you’ve got backups, you’ll be fine. If you don’t, you’re about to go out of business.”

iOS and Swift

swift adjectives

Regrets and mistakes, part two

sad man at computer

From My Biggest Regret as a Programmer:

I could go on and on but the key is that you can’t make changes in how people do things in a technical sense unless you have the ability, the authority and the opportunity. Once you make that call and assuming you find the right places to grow, the sky is really the limit.

When I was on TV (Computer Chronicles) in early 1987 showing our product Trapeze the other presenter was Mike Slade who was product manager of Excel. At the time young me thought him some random marketing weenie (young people can be pretty stupid). Yet he started all these companies later including ESPN, worked for Apple in various leadership roles, was a good friend of Steve Jobs and started his own VC firm.

And today I am still just a programmer. Who’s the weenie now? I doubt I will ever be able to really retire. Thankfully I am still good at delivery (I was recruited into my present job by a former manager who needed what he knew I can do) but still all I will be until I croak is what I am now.