Categories
Swift Kick

How to build an iOS weather app in Swift, part 2: A little more explanation, and turning OpenWeatherMap’s JSON into a Swift dictionary

smartphone weather

Welcome to part two in a series of articles on building your own iOS weather app in Swift!

The previous article in this series showed you how to:

  • Get a key for OpenWeatherMap’s API for current weather data
  • Make a manual API call using your browser
  • Create a basic app to get the data from OpenWeatherMap
  • Tweak the app

In this article, we’ll explain the code that was presented in the previous article and make the next major step in writing our weather app: extracting the data from the JSON returned by OpenWeatherMap.

A deeper look at the code that gets the weather data

In the last installment, I gave you enough code to connect to OpenWeatherMap and retrieve the weather data, but never explained how it works. Let’s fix that oversight.

Our simple weather app makes use of the NSURLSession class along with some classes that go along with it. Together, classes in the NSURLSession family of classes give us an API for downloading and uploading data from the internet, in a number of different ways.

The diagram below shows the classes that our weather app works with, either directly or indirectly:

nsurlsession diagram

Here’s a slightly more in-depth explanation of the classes in the diagram above:

NSURLSession class Instances of NSURLSession are containers — collections of other objects — that provide an API for sending data to and receiving data from a given URL. You use the NSURLSession API to create one or more sessions, which are objects that coordinate one or more tasks, which are objects that actually transfer the data.
Shared session object The shared session object is a pre-made singleton instance of NSURLSession. It’s not as configurable as other NSURLSession instances that you can instantiate yourself, but for simple requests, such as the kind we’re making in our bare-bones weather app, it’s good enough. You access the shared session with NSURLSession‘s sharedSession class method.
NSURLSessionTask class As mentioned in the explanation of NSURLSession above, tasks are the objects within an NSURLSession instance that actually transfer the data. NSURLSessionTask is the base class for tasks.
NSURLSessionDataTask class

You typically don’t instantiate an NSURLSessionTask object, but one of its subclasses, and you do so by calling one of NSURLSession‘s task creation methods. NSURLSessionDataTask is the class for data tasks, which are used to download data from a given server into memory. We’re using an NSURLSessionDataTask instance to request and collect data from OpenWeatherMap.

In addition to data tasks, there are also upload tasks, which are used to upload data to a server, and download tasks, which are used for downloading data from a server into a file (as opposed to memory, which is where data tasks download their data to).

That’s the “big picture” view. Keep it in mind as we look at the code that does the downloading, contained in the getWeatherInfo function in the WeatherGetter class:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "06db44f389d2172e9b1096cdce7b051c"
  
  func getWeatherInfo(city: String) {
    
    // *** 1 ***
    let session = NSURLSession.sharedSession()

    // *** 2 ***
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // *** 3 ***
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      // *** 4 ***
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      // *** 5 ***
      else {
        // Case 2: Success
        // We got a response from the server!
        print("Raw data:\n\(data!)\n")
        let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
        print("Human-readable data:\n\(dataString!)")
      }
    }

    // *** 6 ***
    dataTask.resume()
  }
  
}

I’ve added number comments to the code, which correspond to the explanations below:

  1. We use NSURLSession‘s class method sharedSession to get a reference to the shared session object, and assign that reference to the local variable session.
  2. We construct an URL using the URL for OpenWeatherMap’s current weather API, filling in the blanks with the name of the city that we want the weather for, and our OpenWeatherMap API key.
  3. We want to use a data task to request and retrieve the data from OpenWeatherMap. As mentioned earlier, tasks are created by using one of NSURLSession‘s task-creation methods. In this case, we’re using dataTaskWithURL, which we provide with 2 arguments:
    • The URL created in step 2, and
    • A completion handler that executes once the task is done requesting and retrieving the data. The completion handler will receive three arguments, which we can work with:
      1. data: If the request was successfully made and the data was successfully received, this will contain that received data.
      2. response: The response from the server.
      3. error: If the request was not made successfully or the data was not received successfully, this will contain information about the error.
  4. If the error argument isn’t nil, we assign its value to a local variable also named error, and then display its contents.
  5. If the error argument is nil, it means that no error occurred, and we display both the server response and the data received, in both raw and human-readable formats.
  6. Up until this point, we’ve only defined the data task. This statement activates it.

Turning the weather data into a Swift dictionary

Right now, if the weather request was successfully made and the data was successfully received, we take that data, convert it into a human-readable string, and output it to the debug console…

// Case 2: Success
// We got a response from the server!
print("Raw data:\n\(data!)\n")
let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
print("Human-readable data:\n\(dataString!)")

…and that human-readable string looks something like this:

{"coord":{"lon":-82.46,"lat":27.95},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"cmc stations","main":{"temp":295.49,"pressure":1015,"humidity":64,"temp_min":294.15,"temp_max":296.15},"wind":{"speed":2.1,"deg":280},"clouds":{"all":1},"dt":1460163717,"sys":{"type":1,"id":728,"message":0.004,"country":"US","sunrise":1460200241,"sunset":1460245929},"id":4174757,"name":"Tampa","cod":200}

We could simply perform all sorts of string operations to extract the data we need, but why should we? The data is already in JSON format, which maps nicely to data structures in most programming languages, including Swift. There should be a simple way to take that incoming data and turn it into a nice Swift dictionary.

Enter the NSJSONSerialization class, which can convert JSON into Foundation objects (such as arrays and dictionaries), and vice versa. It has a method called JSONObjectWithData that takes two arguments:

  • data: An NSData object containing JSON data, which we happen to have as one of the parameters of our completion handler, and
  • options: Options that specify how the JSON data should be read and how the corresponding Foundation objects should be created.

Let’s change the code so that we no longer turn the data into a string, but instead using NSJSONSerialization‘s JSONObjectWithData method to turn it into a dictionary:

// Case 2: Success
// We got a response from the server!
do {
  // Try to convert that data into a Swift dictionary
  let weather = 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 print its contents to the debug console.
  print("Date and time: \(weather["dt"]!)")
  print("City: \(weather["name"]!)")
  
  print("Longitude: \(weather["coord"]!["lon"]!!)")
  print("Latitude: \(weather["coord"]!["lat"]!!)")

  print("Weather ID: \(weather["weather"]![0]!["id"]!!)")
  print("Weather main: \(weather["weather"]![0]!["main"]!!)")
  print("Weather description: \(weather["weather"]![0]!["description"]!!)")
  print("Weather icon ID: \(weather["weather"]![0]!["icon"]!!)")

  print("Temperature: \(weather["main"]!["temp"]!!)")
  print("Humidity: \(weather["main"]!["humidity"]!!)")
  print("Pressure: \(weather["main"]!["pressure"]!!)")

  print("Cloud cover: \(weather["clouds"]!["all"]!!)")

  print("Wind direction: \(weather["wind"]!["deg"]!!) degrees")
  print("Wind speed: \(weather["wind"]!["speed"]!!)")

  print("Country: \(weather["sys"]!["country"]!!)")
  print("Sunrise: \(weather["sys"]!["sunrise"]!!)")
  print("Sunset: \(weather["sys"]!["sunset"]!!)")
}
catch let jsonError as NSError {
  // An error occurred while trying to convert the data into a Swift dictionary.
  print("JSON error description: \(jsonError.description)")
}

If you run the app now, you’ll see that when put into dictionary form, it’s easy to extract weather data:

Date and time: 1462277829
City: Tampa
Longitude: -82.45999999999999
Latitude: 27.95
Weather ID: 800
Weather main: Clear
Weather description: clear sky
Weather icon ID: 02d
Temperature: 294.659
Humidity: 95
Pressure: 1025.47
Cloud cover: 8
Wind direction: 168.003 degrees
Wind speed: 2.11
Country: US
Sunrise: 1462272456
Sunset: 1462320359

Here’s what the complete WeatherGetter.swift should look like now:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "YOUR API KEY HERE"
  
  func getWeather(city: String) {
    
    // This is a pretty simple networking task, so the shared session will do.
    let session = NSURLSession.sharedSession()
    
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // The data task retrieves the data.
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      else {
        // Case 2: Success
        // We got a response from the server!
        do {
          // Try to convert that data into a Swift dictionary
          let weather = 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 print its contents to the debug console.
          print("Date and time: \(weather["dt"]!)")
          print("City: \(weather["name"]!)")
          
          print("Longitude: \(weather["coord"]!["lon"]!!)")
          print("Latitude: \(weather["coord"]!["lat"]!!)")

          print("Weather ID: \(weather["weather"]![0]!["id"]!!)")
          print("Weather main: \(weather["weather"]![0]!["main"]!!)")
          print("Weather description: \(weather["weather"]![0]!["description"]!!)")
          print("Weather icon ID: \(weather["weather"]![0]!["icon"]!!)")

          print("Temperature: \(weather["main"]!["temp"]!!)")
          print("Humidity: \(weather["main"]!["humidity"]!!)")
          print("Pressure: \(weather["main"]!["pressure"]!!)")

          print("Cloud cover: \(weather["clouds"]!["all"]!!)")

          print("Wind direction: \(weather["wind"]!["deg"]!!) degrees")
          print("Wind speed: \(weather["wind"]!["speed"]!!)")

          print("Country: \(weather["sys"]!["country"]!!)")
          print("Sunrise: \(weather["sys"]!["sunrise"]!!)")
          print("Sunset: \(weather["sys"]!["sunset"]!!)")
        }
        catch let jsonError as NSError {
          // An error occurred while trying to convert the data into a Swift dictionary.
          print("JSON error description: \(jsonError.description)")
        }
      }
    }
    
    // The data task is set up...launch it!
    dataTask.resume()
  }
  
}

At this point, the WeatherGetter class doesn’t just get the weather data from OpenWeatherMap; it also puts that data into a form that we can process: a Dictionary. We’re still displaying the information in the debug console — we’re still not showing it to anyone who’s not running the app with Xcode.

In the next installment in this series, we’ll take the weather data, now in dictionary form, and make it visible to the user. We’ll also make it possible for the user to enter a city to get the weather for, rather than hard-wire it into the app.

xcode download

You can download the project files for this aricle (41KB zipped) here.

Categories
Uncategorized

“The Unbeatable Squirrel Girl”: The comic book that tips its hat to computer science

the unbeatable squirrel girl cover

Marvel Comics are doing some interesting things with characters that aren’t that well-known outside comic book fandom, not just with the Guardians of the Galaxy, a (very radically altered) Big Hero 6, and Ant-Man on the big screen and Jessica Jones, Peggy Carter, and various agents of S.H.I.E.L.D. on smaller ones, but even in their comic books, with Ms. Marvel and, more recently, the incredibly cute and unbeatable (it says right so in the title) Squirrel Girl.

Conceived as a throw-away character in the early ’90s when most comic book series were trying to be like The Dark Knight Returns, the 1982 Wolverine solo series, or the X-Men, Squirrel Girl has become important enough to merit her own book, The Unbeatable Squirrel Girl, written by Ryan North of Dinosaur Comics fame.

Ryan North has a computer science background, with a bachelor’s degree from Ottawa’s Carleton University and a master’s from the University of Toronto (that’s right, he’s Canadian). That background is what drives some of Squirrel Girl’s story; when she’s not fighting criminals and supervillains, she’s Doreen Green, first-year student (that’s a Canadianism; most Americans would say freshman) of computer science at Empire State University.

Sometimes her computer science studies are the backdrop, as in this scene, where she shows up a little bit late for her class on databases…

squirrel girl database class 1

squirrel girl database class 2

squirrel girl database class 3

…and other times, it plays a key role in defeating A-list supervillains like Victor von Doom. Doom’s primary flaw is that he’s a supreme egomaniac. Although he’s a science and engineering genius, he’s too proud to have learned programming languages that weren’t of his own design.

So when Squirrel Girl sends a message in C++ to her friends backing her up…

squirrel girl programming 1

Click the comic to see it at full size.

(By the bye, here’s the code that she called out…)

string lw(int arr[], int arrsize) {
  string ret = "";
  for (int i = 0; i < arrsize; i++) {
    ret += itoa(arr[i]);
    return ret;
  }
}

lw({90, 65, 80, 77, 69, 87, 84, 73, 77, 
69, 77, 65, 67, 72, 73, 78, 69, 80, 76, 
90}, 20) + "!!!!!";

…Doom doesn’t understand what she’s saying, since he only programs robots in his own “Doomsembly language” and never learned any real programming languages because it would mean learning from others…

squirrel girl programming 2

Click the comic to see it at full size.

Before you go spoiling the fun by saying “there’s no way you can formulate and yell out that code in that little time, and then have a friend do ASCII conversions in her head that quickly”, remember that all this is happening in a confrontation between a villain in a mechanical suit powered by both super-science and sorcery and a young woman with squirrel powers.

Also amusing: how the comic book often starts with Twitter conversations:

squirrel girl twitter

In case you’re wondering, the jingle she’s referring to is from the 1960’s Iron Man cartoon…

…and it gets referenced in the first Iron Man movie as his mobile phone ringtone…

The Unbeatable Squirrel Girl is a fun read, and the computer science bits just make it even more fun for a former computer science major like myself.

Categories
Uncategorized

Mobile news roundup: Procurement fraud, Mobile ate the world, US mobile network performance, and mobile electronic flight bags for pilots

Keeping an eye out for mobile device procurement fraud

 

mobile phone fraud

In the CFO.com article, Cell Phone Fraud: Who’s Watching IT?, corporate forensics specialists Jesse Daves and Celyna Frost talk about the very profitable business of mobile device procurement fraud. With aftermarket prices for a new iPhone 6S ranging from $1,000 in the U.S. to as much as $3,000 in China, it’s often tempting for people in IT departments to use their authority and access to procure mobile devices on their company’s behalf using company funds, make them disappear from the books and resell them at a completely cost-free profit.

Without the internal controls and systems to provide visibility into its inventory and procurement process, it’s much simpler for internal fraud. The article lists a number of “red flags” that indicate the potential for procurement fraud, including:

  • IT’s resistance to sharing access to the mobile provider’s billing web portal
  • When accounts payable receives only a summary of the mobile bill
  • Blanket charges that are simply summarized as “international data charges” or “roaming fees” without any substantiation
  • An unusual number of suspended or frozen accounts
  • Unusually large orders of devices
  • Recurring shipments to unknown parties

Suggestions for reducing the likeliness of mobile procurement fraud include:

  • Separating responsibilities so that employees with the authority to order equipment are not the same ones as those who receive them upon delivery
  • Controlling the process for payment approval and cost allocations (one fraud trick is to allocate costs for improperly-purchased goods to another business unit)
  • Maintaining comprehensive and complete records so that every item on every bill can be traced back to a device, circuit, or service in the company’s telecom inventory
  • Performing regular audits to ensure that all telecom charges can be connected to valid employees.

In the end, clear visibility into your telecom environment is your best defense against internal fraud.

Mobile Ate the World: A presentation by Andreesen Horowitz

Benedict Evans of $4 billion venture capital firm Andreesen Horowitz gave a presentation at The Guardian’s Changing Media Summit titled Mobile Ate the World, a play on his firm’s motto, “Software is eating the world”.

In it, he talks about how mobile is now the universal technology, scaling up to everyone on earth…

mobile is the new tech ecosystem

…how mobile isn’t just a screen size, but an ecosystem…

mobile is not a screen size

 

…and how mobile doesn’t just mean “mobile” — a lot of mobile use happens at home, and nearly 50% of smartphone traffic happens on wifi:

mobile doesnt mean just mobile

If you’re as interested in the future of mobile as we are, it’s worth reading Mobile Ate the World.

RootMetrics’ Mobile Network Performance in the US report

rootmetrics charts

The cellular carriers are in hot competition and expanding the reach of their LTE networks and grow their network capacities — how are they doing, and who’s offering the fastest and most reliable mobile experiences? The cellular analytics firm RootMetrics published regular reports on this topic, and their latest one looks at the service offered by AT&T, Sprint, T-Mobile, and Verizon in the second half of 2015.

ExpressJet replaces 50-pound flight bags with Surface tablets running Windows 10

pilot's flight bag

If you fly often, you’ve probably seen pilots walking to or from their flights lugging large bags like one pictured above (perhaps not as well-worn). These flight bags have traditionally carried a lot of paper documentation in binders: operating manuals, navigation charts, reference handbooks, flight checklists, logbooks and weather information, and together, they can weigh anywhere from 30 to 50 pounds. This documentation is updated regularly, so often before each flight, pilots have to collect updated documentation, remove the outdated material from their binders and insert the new pages. It’s a lot of paperwork to lug around and sort through; now imaging trying to find a key piece of information while you’re trying to fly a plane at the same time.

expressjet pilot and surface 3

As far back as 2011 — a mere year after the introduction of the iPad — airlines have been replacing all this paper documentation with “EFBs” (Electronic Flight Bags), which are tablets running apps specifically designed for pilots. United Airlines committed to deploying 11,000 iPads running Jeppesen’s FliteDeck app to pilots as their EFBs in the late summer of 2011, and more recently, ExpressJet got FAA approval to hand out Surface 3s running Windows 10 to their pilots to replace all that paper that went into their flight bags.

Here’s a video by Microsoft with ExpressJet pilot Renee Devereux talking about her new Surface 3 EFB:

The electronic flight bag is a good example of mobile devices playing to their strengths in the workplace:

  • As electronic replacements for large volumes of often-updated paper documents
  • Providing quick access to crucial data in routine and (literally!) mission-critical scenarios
  • Portable computing devices with significantly greater battery life than traditional laptops
  • An employee perk (as shown in the video, ExpressJet pilots are allowed to use their tablets to stay in touch with their families while on the “road”)

this article also appears in the GSG blog

Categories
Uncategorized

Mobile developer news roundup: Making money (or not) with your apps

smartphone money

This roundup of mobile developer news has a theme: making money with your apps, with both practical advice and tutorials, as well as bigger-picture discussions of the topic.

under the radar

Under the Radar is David Smith and Marco Arment’s podcast on indie iOS app development. They make sure that none of their podcasts runs longer than half an hour. Their latest podcast, the 21st in the series, is titled App Store Rejection, in which they provide suggestions on how to avoid rejections from Apple’s App Store review staff, and what to do when your app gets rejected.

Their two previous podcasts also cover the App Store: Improving the App Store, Part 1 and Improving the App Store, Part 2.

angry birds

I had no idea that mobile games were a bigger business than PC and console gaming. Here are the sizes of the game markets, according to App Annie and IDC:

  • Mobile games are a $34.8 billion market worldwide,
  • PC/Mac games are a $29 billion market,
  • Console games are an $18.5 billion market, and
  • Games for handheld game devices are a $3 billion market.

At the time of writing,”Application Category Distribution” section of PocketGamer.biz’s App Store metrics page showed the following breakdown of apps:

  1. Games (527,017 active)
  2. Business (234,930 active)
  3. Education (210,991 active)
  4. Lifestyle (198,844 active)
  5. Entertainment (144,613 active)

They also report these numbers for app pricing:

  • Current Average App Price: $1.12
  • Current Average Game Price: $0.54
  • Current Average Overall Price: $0.99

android apps for sale

Ray Wenderlich’s site may have started out as iOS-specific, but they’ve expanded their coverage to include Android and Unity development. Their latest Android article is titled Android App Distribution Tutorial: From Zero to Google Play Store, and it shows you how to get your newly-created Android app into Google Play’s store.

google play and app store

App Annie, the mobile app analytics service, published their inaugural app economy forecast a couple of months ago, where they make the following predictions:

  • This year, the global app market is expected to grow 24% and hit $51 billion in gross revenues across all app stores
  • The global app market will exceed $100 billion is gross revenues by 2020
  • China surpassed the US in mobile app downloads last year, and is expected to surpass them in app spending this year.

in-app purchase

In-app purchases can help you generate more money with your apps, and this article can help you with setting them up on iOS apps: In-App Purchase Tutorial: Getting Started.

life and death in the app store

In case you missed it, The Verge recently published Life and Death in the App Store, an article which on one level is the story of Pixite, a mobile development shop that had some early successes and is now treading water, and on another level is the story of the changes in the app market between its inception less than a decade ago and the present day, and what that means for developers who plan to make a living off it.

Categories
Uncategorized

How to write your own TSA security line randomizer app (and maybe make sweet government contract money)

tsa randomizer

About 150 airports in the U.S. have TSA PreCheck, an expedited security line for pre-screened travellers. If you fly often, you’ll find it very convenient as the line’s usually short, and you can forego having to remove your shoes, jacket, and belt, as well as having to separate your laptop and “3-1-1 liquids” from the rest of your carry-on luggage. There is a price to be paid for this — a registration fee, along with providing the U.S. Customs and Border Protection agency enough info to do a background check on you, and there’s an interview as well. If you’re an American or Canadian with a NEXUS card, you’re already in the TSA PreCheck program.

Even the pre-screened people going through TSA PreCheck get some additional random screening every now and again. The TSA PreCheck line often has two lanes, one on the left, one on the right, and they pick people for additional security screenings from one of them. In order to keep people from trying to guess which line that is, the TSA commissioned the development of a “randomizer”, which is an iPad app that randomly determines which line you should use.

The video below shows the randomizer in action:

The app would make a good “iPad programming 101” assignment; a newbie developer should be able to complete it in a day, and someone with even a couple of months’ of experience should be able to complete it in an hour.

Developer Kevin Burke wondered how much the TSA paid to have the app developed. As a government agency, they’d have put out an RFP to companies that do contracts for government work, and none of them come cheap. He filed a Freedom of Information Act request, and mirabile dictu, he got some answers in the form of two documents:

Here’s the most eye-catching part of the contract:

total award amount

Burke posted his findings online, and Pratheek Rabala, who does interactive graphics for TIME, pointed out that not only the information was readily available to the public, but that the payment shown above was just one in a series of payments totalling $1.4 million.

It turns out that the $1.4 million dollars covers the cost of a larger project, which includes other mobile applications, software to support an “enhanced staffing model” for the TSA, and all the project management that such an undertaking requires.

The TSA, when asked, pointed out that the “development costs for the TSA Randomizer Application were $47,400 in total.”

With that in mind, it’s time to fire up Xcode and start on the path to that sweet, sweet government contract money. Why should IBM have all the fun?

If you’re new to iOS programming or even programming in general, the TSA Randomizer app is a good starter project. You might want to try writing it on your own before following the steps below. Here’s a hint that should help — you’ll need to know how to do these things in order to write the app:

  • How to generate a random number
  • How to display an image on the screen
  • How to change the contents of an image on the screen
  • How to know when the user has tapped on the screen

How to write the app

xcode icon

Open up Xcode and do the FileNewProject… dance. As you may have already suspected, you’re going to specify that this is a Single View Application:

tsa 01

Give the project a name (I called mine TSARandomizer), and select iPad from the Devices drop-down menu:

tsa 02

Once you’ve chosen a place to save the project and it’s been written to disk, you’ll be taken to the General page for your project. Under Device Orientation, make sure that Portrait is checked and that all other orientations are unchecked:

tsa 04

Click the screenshot to see it at full size.

Edit ViewController.swift and change its contents to the following:

import UIKit

class ViewController: UIViewController {
  
  @IBOutlet weak var arrowImageView: UIImageView!
  
  let leftArrowImage = UIImage(named: "arrow-left")!
  let rightArrowImage = UIImage(named: "arrow-right")!

  override func viewDidLoad() {
    super.viewDidLoad()

    let tapGestureRecognizer = UITapGestureRecognizer(target:self, action:#selector(arrowImageViewTapped))
    arrowImageView.userInteractionEnabled = true
    arrowImageView.addGestureRecognizer(tapGestureRecognizer)
    
    displayRandomizedArrow()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }
  
  func arrowImageViewTapped() {
    displayRandomizedArrow()
  }
  
  func displayRandomizedArrow() {
    if arc4random_uniform(10) < 5 {
      arrowImageView.image = leftArrowImage
    }
    else {
      arrowImageView.image = rightArrowImage
    }
  }

}

This is all the logic needed to make the app work. The displayRandomizedArrow method is currently set so that 50% of the time, the left arrow is selected, and the other 50% of the time, the right arrow appears. You can adjust this to taste.

Time to build what little user interface there is. Open Main.storyboard and put an Image View control on the view. This will contain the arrows. We want it to fill up the screen, so set its constraints so that all its sides are 20 pixels from the view:

tsa 05

Click the screenshot to see it at full size.

In our code, we have an outlet variable named arrowImageView. We need to connect it to the image view we just added. Select the view and open the Connections Inspector. Drag from the circle to the right of New Referencing Outlet to the View Controller icon above the view, as shown in the screenshot below.

tsa 06

Click the screenshot to see it at full size.

Select arrowImageView from the pop-up menu that appears.

tsa 07

Click the screenshot to see it at full size.

Finally, we need to add the arrow images to our app: arrow-left.png and arrow-right.png. Open Assets.xcassets and drag the left-arrow and right-arrow images into it.

tsa 08

Click the screenshot to see it at full size.

Fire it up in the simulator or deploy it to your iPad, and start bidding on some TSA RFP’s — you’ve got a functioning TSA randomizer app!

xcode download

I’ve zipped up my project and made it available for download (it’s a mere 300KB zipped). Enjoy!

Categories
Uncategorized

A quick test run with the new Xamarin Studio Community for Mac

xamarin forms

One of the big changes that came from Microsoft’s acquisition of the cross-platform development tool company Xamarin is that their IDE, which once required a subscription fee for a version for building non-trivial projects, is now free-as-in-beer for the following:

  • individual developers,
  • small professional teams (5 or fewer users in an organization that isn’t considered an “enterprise”, which Microsoft defines an organization with more than 250 PCs or makes more than US$1 million in annual revenues),
  • open source projects (that is, projects released under Open Source Initiative-approved licences),
  • use in on-line or in-person classroom training and education, and
  • use in academic research projects

If Windows is your development platform, Xamarin’s cross-platform mobile dev tools come baked into all editions of Visual Studio, even the free-as-in-beer Community Edition, which you can download from the Visual Studio page. If your preferred development platform is Mac OS, Xamarin Studio, a full-featured stand-alone IDE that borrows a few tricks from Visual Studio. In this article, I’ll take you on a quick tour of Xamarin Studio Community, as seen on my MacBook Pro, iPhone 6S, and Moto G running Android Marshmallow.

The installation process is pretty straightforward, and the length of time it takes will depend largely on your internet connection speed. Here’s a screenshot that I took of the installer at the start of the process:

xamarin installer

Once installed, I did the oh-so-familiar FileNewSolution… dance and was presented with this window:

xamarin 2016 01

Note the options offered in the left column:

  • Cross-platform development, which you can do in one of two ways:
    • Using Xamarin.Forms, a “universal” UI framework that allows you to target a single cross-platform UI that tries to be a native as possible on both Android and iOS, and
    • a more “traditional” way, where you write Android- and iOS-specific UI code while writing common logic that both with use
  • iOS apps,
  • Android apps,
  • Mac applications, and
  • “other” applications, which include:
    • console projects,
    • GTK# 2.0 projects,
    • NUnit libraries,
    • F# tutorials,
    • ASP.NET and ASP.NET MVC projects

For this walkthrough, I’m going to go with Xamarin.Forms to write something to deploy to both Android and iOS. When you do this, you’re presented with a couple of configuration dialogs, shown below. I’m going to name my project Magic8Ball

xamarin 2016 02

xamarin 2016 03

Once past the configuration dialogs, you’re presented with your project. Xamarin Studio’s interface is a pretty standard IDE: solution tree in the left column, editor in the upper right-hand quadrant, and debug and console panes below the editor:

xamarin 2016 04

If you look at the left-hand solution tree column, you should notice that it’s divided into three parts:

  1. Magic8Ball, the main project
  2. Magic8Ball.Droid, the Android-specific parts of the solution, and
  3. Magic8Ball.iOS, the iOS-specific parts of the solution.

Right now, Magic8Ball.iOS is marked in bold, which indicates that it’s the startup project. If you were to run the app right now with an iOS simulator as the target, here’s what you’d see:

xamarin 2016 05

Left-clicking on Magic8Ball.Droid and selecting Set As Startup Project makes it the startup project…

xamarin 2016 06

…and here’s what it looks like on the emulator. Note that Xamarin’s Android projects, by default, use a dark color scheme (which you can change with a simple configuration tweak):

xamarin 2016 09

In the newly-created “Hello World” template that you get when you first create a Xamarin.Forms solution, everything happens in the Application class instance. In the case of my app, that instance lives in Magic8Ball.cs. Not how the entire UI is set up in the constructor, App():

using System;
using Xamarin.Forms;

namespace Magic8Ball
{

	public class App : Application
	{
	
		public App ()
		{
			// The root page of your application
			MainPage = new ContentPage {
				Content = new StackLayout {
					VerticalOptions = LayoutOptions.Center,
					Children = {
						new Label {
							XAlign = TextAlignment.Center,
							Text = "Welcome to Xamarin Forms!"
						}
					}
				}
			};
		}

		protected override void OnStart ()
		{
			// Handle when your app starts
		}

		protected override void OnSleep ()
		{
			// Handle when your app sleeps
		}

		protected override void OnResume ()
		{
			// Handle when your app resumes
		}

	}

}

I changed the code to pass off the UI creation to a view class that I’m calling MainPage:

using System;
using Xamarin.Forms;

namespace Magic8Ball
{
	public class App : Application
	{

		public App ()
		{
			// The root page of your application
			MainPage = new MainPage();
		}

		protected override void OnStart ()
		{
			// Handle when your app starts
		}

		protected override void OnSleep ()
		{
			// Handle when your app sleeps
		}

		protected override void OnResume ()
		{
			// Handle when your app resumes
		}
	}

}

I decided to create a class called EightBall to handle all the predictions. Using File → New → File…, I was presented with the New File dialog, where I selected General on the left-hand column and Empty Class in the center column:

xamarin empty class

Here’s the contents of the file as given to me by Xamarin…

using System;

namespace Magic8Ball
{
	public class EightBall
	{
		public EightBall ()
		{
		}
	}
}

…and here it is once I’d filled it in:

using System;
using System.Collections.Generic;

namespace Magic8Ball
{
	public class EightBall
	{
		List<string> answers = new List<string>() {
			"Yes.",
			"Sure thing.",
			"But of course!",
			"I'd bet on it.",
			"AWWW YISSS!",
			"No.",
			"Nuh-uh.",
			"Absolutely not!",
			"I wouldn't bet on it",
			"HELL NO.",
			"Maybe",
			"Possibly...",
			"Ask again later.",
			"I can't be certain.",
			"Clouded by the Dark Side, the future is."
		};

		Random randomAnswerSelector = new Random();

		public string getAnswer() {
			return answers[randomAnswerSelector.Next(answers.Count)];
		}

	}
}

With the model class done, it was time for a view class, created with File → New → File… again. This time, I selected Forms from the left-hand column, and Forms ContentPage from the center column, specifying that I wanted a ContentPage, an onscreen container for containing a single view. It’s often used to represent a “screen” in an app:

xamarin new file

Here’s the contents of the file when initially generated by Xamarin…

using System;

using Xamarin.Forms;

namespace Magic8Ball
{
	public class MainPage : ContentPage
	{
		public MyPage ()
		{
			Content = new StackLayout { 
				Children = {
					new Label { Text = "Hello ContentPage" }
				}
			};
		}
	}
}

…and here it is once I’ve defined my user interface and added a reference to the EightBall class:

using System;
using Xamarin.Forms;

namespace Magic8Ball
{
	public class MainPage : ContentPage
	{
		EightBall eightBall = new EightBall();

		public MainPage ()
		{
			var layout = new StackLayout {
				Padding = 20,
				VerticalOptions = LayoutOptions.Center
			};

			var answerButton = new Button {
				Text = "  I need an answer!  ",
				BackgroundColor = Color.Blue,
				TextColor = Color.White,
				FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button)),
				FontAttributes = FontAttributes.Bold,
				HorizontalOptions = LayoutOptions.CenterAndExpand
			};

			var answerLabel = new Label {
				Text = " ",
				FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
				FontAttributes = FontAttributes.Bold,
				HorizontalOptions = LayoutOptions.CenterAndExpand,
				XAlign = TextAlignment.Center
			};

			answerButton.Clicked += (object sender, EventArgs e) => {
				answerLabel.Text = eightBall.getAnswer();
			};

			layout.Children.Add (answerButton);
			layout.Children.Add (answerLabel); 

			Content = layout;
		}
	}
}

…and that’s it — we have a basic “Magic 8-Ball” app! Here’s what it looks like running on iOS 9…

ios 8ball screen shot

Screenshot taken on my iPhone 6S running iOS 9.2.

…and here’s what it looks like running on Android Marshmallow:

android 8ball screen shot

Screenshot taken on my Moto G (second generation) running Android Marshmallow (6.0).

xamarin download

You can download the project (70KB zipped) to try it for yourself.

More about Xamarin

creating mobile apps with xamarin forms

Charles Petzold, the guy who literally wrote the book on Windows development, was commissioned to write a book on building cross-platform mobile apps with Xamarin.Forms. At 27 chapters and nearly 1,200 pages, Creating Mobile Apps with Xamarin.Forms is the most complete document on the topic, and it’s free-as-in-beer to download. You’ll definitely want to get your hands on this.

edx

For those of you looking for an online course, edX is offering a course called Introduction to Xamarin.Forms. It’s an introductory-level course, and is available for free. However, if you’d like to get a “Verified Certificate” for completing the course, you can apply for one for a mere $25.

build 2016

The two videos below, shot at Microsoft’s recent Build 2016 conference, show more about Xamarin and its tools in action. Here’s the shorter one (27:19 long)…

…and here’s the longer one, running at one hour, which features a complete session from the conference, Cross-Platform Mobile with Xamarin:

Categories
Uncategorized

How to build an iOS weather app in Swift, part 1: A very bare-bones weather app

optimistic weather app

Screenshots from the Optimistic Weather app. Click the image to see the app in the App Store.

According to comScore, two of the top 25 most-used mobile apps were weather apps, namely the Weather Channel app (#16) and the Weather Channel widget (#20). This makes sense, as a weather app fits at least two, if not three, of the book Tapworthy says are the mobile users’ mindsets when they launch an app:

  1. I’m bored
  2. I’m local
  3. I’m micro-tasking

Weather apps are great programming exercises, as they combine a number of different features, including fetching information from online sources, and geolocation. In this series, we’ll build a weather app from scratch, starting with a simple, no-user-interface, only-developers-could-love, bare-bones weather app, and over time, turn it into something better.

This first article looks at where we’ll get the weather data for our app and signing up for a free subscription to that weather source. We’ll then retrieve weather data from that source, first manually, then programatically. At the end of this article, you’ll have a very basic weather app; it won’t have a user interface, and the way it presents information is more developer-friendly than user-friendly, but it will perform a key task: retrieve the current weather for a specified location.

Step 1: Get an OpenWeatherMap API key for current weather data

openweathermap

There are a number of weather APIs to choose from, and for this tutorial, we’ll use OpenWeatherMap’s API. It’s simple to use, and you can make up to 60 calls a minute on their free API subscription plan, which should be more than enough for testing purposes and personal use.

If you go to OpenWeatherMap’s API page (pictured below), you’ll see that they offer all sorts of weather data. For this app, which answers the question “What’s the weather like right now for a given place?”, we’ll use their current weather data API. Click on the API doc button under Current weather data:

openweathermap apis

and you’ll be taken to this page. Under the Free column, click the Get API key and Start button:

openweathermap current weather api

which will take you to yet another page. Click the Sign up button…

opernweathermap sign up

…which takes you to a page where you provide a little info:

openweathermap create account form

Once you’ve submitted your info, you’ll have created an OpenWeatherMap account. You’ll be sent to a page that shows your API key (pictured below). If you ever need to look up this hey again, you can log into the site and click on the Home link near the upper right-hand corner of the page, which will take you to your account information, which includes your API key.

openweathermap api key

Copy the API key, and get ready to use it in the next step!

Step 2: Test your API key using your browser

OpenWeatherMap’s current weather data API accepts calls in the format shown below:

http://api.openweathermap.org/data/2.5/weather?q=\CityNameGoesHere&APPID=YourAPIKeyGoesHere

Note the parts in italics; you’ll replace those:

  • CityNameGoesHere is replaced with the name of the city whose current weather data you want to fetch, and
  • YourAPIKeyGoesHere is replaced with your API key.

For example, if you want the weather for Tampa, and your API key is abcdef1234567890 (don’t bother using this key; it’s not real and for example purposes only), you’d use this call:

http://api.openweathermap.org/data/2.5/weather?q=\Tampa&APPID=abcdef1234567890

Try constructing your own call, using the format above, along with Tampa for the city and your API key after the part that goes APPID=, and enter it into your browser’s address bar. You should be taken to a very plain page displaying something that looks something like the text below:

{"coord":{"lon":-82.46,"lat":27.95},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"base":"cmc stations","main":{"temp":296.75,"pressure":1018,"humidity":94,"temp_min":295.15,"temp_max":298.15},"wind":{"speed":1.5},"clouds":{"all":75},"dt":1459224301,"sys":{"type":1,"id":721,"message":0.0078,"country":"US","sunrise":1459250563,"sunset":1459295181},"id":4174757,"name":"Tampa","cod":200}

What you’re looking is current weather data, formatted in JSON. Here’s the same data, reformatted so that it’s a little bit easier to read:

{
"coord": {"lon": -82.46, "lat": 27.95},
"weather": [{"id": 803,
             "main": "Clouds",
             "description": "broken clouds",
             "icon": "04n"}],
"base": "cmc stations",
"main": {"temp": 296.75,
        "pressure": 1018,
        "humidity": 94,
        "temp_min": 295.15,
        "temp_max": 298.15},
"wind": {"speed": 1.5},
"clouds": {"all": 75},
"dt": 1459224301,
"sys": {"type": 1,
        "id": 721,
        "message": 0.0078,
        "country": "US",
        "sunrise": 1459250563,
        "sunset": 1459295181},
"id": 4174757,
"name":"Tampa",
"cod": 200
}

The data that OpenWeatherMap returns is a JSON dictionary. Its keys are:

  • coord: A dictionary containing the geographic coordinates for the weather data, with the following keys:
    • lon: Longitude, in degrees
    • lat: Latitude, in degrees
  • weather: An array that usually contains a single dictionary containing the qualitative data about the weather. The dictionary contains the following keys:
    • id: The condition code for the current weather. You can find a list of weather condition codes on this page (you’ll need to scroll down to the section marked Weather condition codes).
    • main: The general category for the current weather. This is usually a single word such as Clear, Clouds, Rain, Thunderstorm, Snow, and so on.
    • description: A slightly longer description of the current weather. Where main may specify Clouds, this value may be few clouds, broken clouds, scattered clouds, etc.
    • icon: The name of the icon for the current weather. You can find a list of icons and their names on this page (you’ll need to scroll down to the section marked Weather icons).
  • base: This is described in the API documentation as an “internal parameter” and probably useful only to OpenWeatherMap’s developers.
  • main: A dictionary containing the quantitative data about the weather. The dictionary contains the following keys:
    • temp: The current temperature, expressed in Kelvin by default.
    • pressure: Atmospheric pressure, expressed in hPa (hectopascal, which is equivalent to 1 millibar). This is the pressure at sea level, if there’s no data for the atmospheric pressure at sea level or ground level.
    • humidity: Humidity, expressed as a percentage.
    • temp_min: Minimum temperature at the moment — the deviation from the current temperature, which you’ll find in large metropolitan areas, expressed in Kelvin by default.
    • temp_max: Maximum temperature at the moment — the deviation from the current temperature, which you’ll find in large metropolitan areas, expressed in Kelvin by default.
    • sea_level: Atmospheric pressure at sea level, expressed in hPa (hectopascal, which is equivalent to 1 millibar).
    • grnd_level: Atmospheric pressure at ground level, expressed in hPa (hectopascal, which is equivalent to 1 millibar).
  • wind: A dictionary containing data about the wind, with the following keys:
    • speed: The wind speed, expressed in meters per second by default.
    • deg: The direction that the wind is coming from, expressed in degrees.
  • clouds: A dictionary containing data about cloud cover, with the following keys:
    • all: The amount of cloud cover, expressed as a percentage.
  • dt: The time when the weather data was provided, in UTC, formatted as Unix time (number of seconds since January 1, 1970, 00:00:00 UTC).
  • sys: A dictionary containing system information, as well as a number of useful items. It contains these keys:
    • type: This is described in the API documentation as an “internal parameter” and probably useful only to OpenWeatherMap’s developers.
    • id: This is described in the API documentation as an “internal parameter” and probably useful only to OpenWeatherMap’s developers.
    • message: This is described in the API documentation as an “internal parameter” and probably useful only to OpenWeatherMap’s developers.
    • country: The two-letter ISO country code for the weather data’s location. Useful when you want to be sure that you’re getting the weather for St. Petersburg in the U.S. or in Russia.
    • sunrise: The time when the sun will rise at the weather data’s location, in UTC, formatted as Unix time (number of seconds since January 1, 1970, 00:00:00 UTC).
    • sunset: The time when the sun will set at the weather data’s location, in UTC, formatted as Unix time (number of seconds since January 1, 1970, 00:00:00 UTC).
  • id: OpenWeatherMap’s internal ID number for the city corresponding to the weather data’s location.
  • name: OpenWeatherMap’s internal name for the city corresponding to the weather data’s location.
  • cod: This is described in the API documentation as an “internal parameter” and probably useful only to OpenWeatherMap’s developers.

Once you’ve confirmed that you can manually get current weather forecasts using your API key and browser, let’s do it programatically with an app.

Step 3: Create a basic app to get the data from OpenWeatherMap

new single view application

Start a new project by doing the FileNewProject… dance and then selecting Single View Application.

Our first iteration won’t have any user interface. It’ll simply try to communicate with OpenWeatherMap, and print the results to the console. Once we’ve confirmed that we’ve got our API communications code working, we’ll add a simple user interface, and I’ll leave making it fancier as an exercise for you.

Many tutorials are happy to stick all the code in a view controller in order to keep the tutorial simple. While it may make writing the tutorial simpler, I think it results in cluttered view controllers and teaches bad programming habits. I’d much rather keep the code in the view controller limited to handling user interactions and put what some people call the “business logic” of our app — in this case, the code that communicates with OpenWeatherMap and extracts weather data from what it sends back — into a separate model class.

With that in mind, we’ll create a new class by selecting FileNewFile… from the menu bar, then selecting Swift File:

new swift class

Give the new file the name WeatherGetter.swift. It will be the home of a class we’ll call WeatherGetter, which will house the code to communicate with OpenWeatherMap and provide the weather data that the view controller will eventually use.

Change the contents of WeatherGetter.swift so that it contains the following code:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "YOUR API KEY HERE"
  
  func getWeather(city: String) {
    
    // This is a pretty simple networking task, so the shared session will do.
    let session = NSURLSession.sharedSession()
    
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // The data task retrieves the data.
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      else {
        // Case 2: Success
        // We got a response from the server!
        print("Data:\n\(data!)")
      }
    }
    
    // The data task is set up...launch it!
    dataTask.resume()
  }
  
}

You’ve probably figured out that you should replace the string YOUR API KEY HERE with the API key that you got in step 1 and used in step 2. You’re probably eager to see this code in action, so we’ll defer the explanation of how it works until we’ve got the app up and running.

We now need to create an instance of the WeatherGetter class and call its getWeather() method. We’ll do it in the viewDidLoad() method of the view controller:

override func viewDidLoad() {
  super.viewDidLoad()

  let weather = WeatherGetter()
  weather.getWeather("Tampa")
}

Now that we’ve got the WeatherGetter class and a way to instantiate and use it, let’s run the app. Remember that right now, it doesn’t have a user interface, and outputs everything using print statements, which will appear in the output pane of XCode’s debug area:

all output will appear here

Step 4: Run the app (and tweak it)

Run the app. You’ll see some disappointing text in the debug area’s output pane that will be similar to this:

2016-04-02 09:03:42.316 SimpleWeather[51150:6547853] App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
Error:
Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSUnderlyingError=0x7f89ab201e20 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "(null)"}, NSErrorFailingURLStringKey=http://api.openweathermap.org/data/2.5/weather?q=Tampa&APPID=***YOUR_APP_ID_HERE***, NSErrorFailingURLKey=http://api.openweathermap.org/data/2.5/weather?q=Tampa&APPID=***YOUR_APP_ID_HERE***, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}

We’ve just run into a security feature of iOS networking that by default requires HTTP connections to be of the secure and encrypted HTTPS variety. Someday, all HTTP communication will be done as HTTPS, but that day hasn’t come yet.

The first thing we should do is see if we can communicate with OpenWeatherMap using HTTPS. Let’s try changing the URL in WeatherGetter.swift so that it starts with https: instead of http:

let weatherRequestURL = NSURL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(city)&APPID=\(openWeatherMapAPIKey)")!

If we run the app again, we get a different, but equally disappointing message in the debug output pane:

Error:
Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the server." UserInfo={NSUnderlyingError=0x7fd6940016c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1004 "(null)" UserInfo={_kCFStreamErrorCodeKey=61, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://api.openweathermap.org/data/2.5/weather?q=Tampa&APPID=***YOUR_APP_ID_HERE***, NSErrorFailingURLKey=https://api.openweathermap.org/data/2.5/weather?q=Tampa&APPID=***YOUR_APP_ID_HERE***, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=61, NSLocalizedDescription=Could not connect to the server.}

So now we know that OpenWeatherMap doesn’t accept HTTPS connection requests. Luckily for us, we can change the default connection security policy for our app so that iOS allows it to communicate using plain HTTP. We can do this by adding a couple of extra rows to Info.plist:

click on ifo.plist

Click the screen shot to see it at full size.

Info.plist is an XML file that contains configuration information, and it’s included in every iOS project. In order to make it more readable, Xcode presents it in a nice list interface. If you prefer, you can edit it in its plain XML form by right-clicking on Info.plist in the Project Navigator and selecting Open As → Source Code in the menu that appears.

To enable our app to use plain HTTP communication, we want to do two things in Info.plist:

  1. Add a new dictionary item with the key App Transport Security Settings
  2. Add an item to the App Transport Security Settings dictionary with the key Allow Arbitrary Loads and the boolean value YES

Rather than describe how you do this, I made a video of the process that should be pretty easy to follow:

In the end, you should have this as part of your Info.plist:

app transport security settings

Once that’s done, don’t forget to change the URL in WeatherGetter.swift so that it starts with http: and not https:

let weatherRequestURL = NSURL(string: "http://api.openweathermap.org/data/2.5/weather?q=\(city)&APPID=\(openWeatherMapAPIKey)")!

Now try running the app. If you’re connected to the internet and OpenWeatherMap.org’s server is running normally, you should see something like the following in the debug output pane:

Data:
<7b22636f 6f726422 3a7b226c 6f6e223a 2d38322e 34362c22 6c617422 3a32372e 39357d2c 22776561 74686572 223a5b7b 22696422 3a323131 2c226d61 696e223a 22546875 6e646572 73746f72 6d222c22 64657363 72697074 696f6e22 3a227468 756e6465 7273746f 726d222c 2269636f 6e223a22 31316422 7d2c7b22 6964223a 3530302c 226d6169 6e223a22 5261696e 222c2264 65736372 69707469 6f6e223a 226c6967 68742072 61696e22 2c226963 6f6e223a 22313064 227d2c7b 22696422 3a373031 2c226d61 696e223a 224d6973 74222c22 64657363 72697074 696f6e22 3a226d69 7374222c 2269636f 6e223a22 35306422 7d5d2c22 62617365 223a2263 6d632073 74617469 6f6e7322 2c226d61 696e223a 7b227465 6d70223a 3239372e 35332c22 70726573 73757265 223a3130 31342c22 68756d69 64697479 223a3838 2c227465 6d705f6d 696e223a 3239362e 31352c22 74656d70 5f6d6178 223a3239 382e3135 7d2c2277 696e6422 3a7b2273 70656564 223a352e 312c2264 6567223a 3138307d 2c22636c 6f756473 223a7b22 616c6c22 3a39307d 2c226474 223a3134 35393630 39363832 2c227379 73223a7b 22747970 65223a31 2c226964 223a3732 352c226d 65737361 6765223a 302e3030 35392c22 636f756e 74727922 3a225553 222c2273 756e7269 7365223a 31343539 35393538 36332c22 73756e73 6574223a 31343539 36343039 32337d2c 22696422 3a343137 34373537 2c226e61 6d65223a 2254616d 7061222c 22636f64 223a3230 307d>

At least now we’re getting some data. Let’s make it a little more human-readable. In WeatherGetter.swift, let’s tweak the else clause so it looks like this:

else {
  // Case 2: Success
  // We got a response from the server!
  print("Raw data:\n\(data!)\n")
  let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
  print("Human-readable data:\n\(dataString!)")
}

Now, when we run the code — and assuming your internet connection and the OpenWeatherMap server are working — the output should look something like this:

Raw data:
<7b22636f 6f726422 3a7b226c 6f6e223a 2d38322e 34362c22 6c617422 3a32372e 39357d2c 22776561 74686572 223a5b7b 22696422 3a323131 2c226d61 696e223a 22546875 6e646572 73746f72 6d222c22 64657363 72697074 696f6e22 3a227468 756e6465 7273746f 726d222c 2269636f 6e223a22 31316422 7d2c7b22 6964223a 3530312c 226d6169 6e223a22 5261696e 222c2264 65736372 69707469 6f6e223a 226d6f64 65726174 65207261 696e222c 2269636f 6e223a22 31306422 7d2c7b22 6964223a 3730312c 226d6169 6e223a22 4d697374 222c2264 65736372 69707469 6f6e223a 226d6973 74222c22 69636f6e 223a2235 3064227d 5d2c2262 61736522 3a22636d 63207374 6174696f 6e73222c 226d6169 6e223a7b 2274656d 70223a32 39372e36 322c2270 72657373 75726522 3a313031 332c2268 756d6964 69747922 3a313030 2c227465 6d705f6d 696e223a 3239362e 31352c22 74656d70 5f6d6178 223a3239 392e3135 7d2c2277 696e6422 3a7b2273 70656564 223a342e 362c2264 6567223a 3138307d 2c22636c 6f756473 223a7b22 616c6c22 3a39307d 2c226474 223a3134 35393631 30323932 2c227379 73223a7b 22747970 65223a31 2c226964 223a3638 302c226d 65737361 6765223a 302e3030 34392c22 636f756e 74727922 3a225553 222c2273 756e7269 7365223a 31343539 35393538 36322c22 73756e73 6574223a 31343539 36343039 32337d2c 22696422 3a343137 34373537 2c226e61 6d65223a 2254616d 7061222c 22636f64 223a3230 307d>

Human-readable data:
{"coord":{"lon":-82.46,"lat":27.95},"weather":[{"id":211,"main":"Thunderstorm","description":"thunderstorm","icon":"11d"},{"id":501,"main":"Rain","description":"moderate rain","icon":"10d"},{"id":701,"main":"Mist","description":"mist","icon":"50d"}],"base":"cmc stations","main":{"temp":297.62,"pressure":1013,"humidity":100,"temp_min":296.15,"temp_max":299.15},"wind":{"speed":4.6,"deg":180},"clouds":{"all":90},"dt":1459610292,"sys":{"type":1,"id":680,"message":0.0049,"country":"US","sunrise":1459595862,"sunset":1459640923},"id":4174757,"name":"Tampa","cod":200}

As you can see, the stuff after “Human-readable data:” looks like the output you got when you manually got the weather data from OpenWeatherMap in step 2.

Step 5: Play with the app, and keep an eye open for the next installment in this series!

Here’s what your WeatherGetter.swift file should look like…

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "YOUR API KEY HERE"
  
  func getWeather(city: String) {
    
    // This is a pretty simple networking task, so the shared session will do.
    let session = NSURLSession.sharedSession()
    
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // The data task retrieves the data.
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      else {
        // Case 2: Success
        // We got a response from the server!
        print("Raw data:\n\(data!)\n")
        let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
        print("Human-readable data:\n\(dataString!)")
      }
    }
    
    // The data task is set up...launch it!
    dataTask.resume()
  }
  
}

…here’s what ViewController.swift should look like…

import UIKit

class ViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let weather = WeatherGetter()
    weather.getWeather("Tampa")
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }
  
}

…and if you prefer to edit Info.plist as raw XML, here’s what it should look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
</dict>
</plist>

xcode download

You can also download the Xcode project — it’s a mere 33KB zipped.

Try some experiments. Here are a few of my suggestions:

  • What happens if you run the app in Airplane Mode?
  • What happens if you change the URL in WeatherGetter to a non-existent one, such as openweathermoop.org?
  • How would you go about extracting the weather information from the JSON returned by the server?
  • How would you display the weather information to the user?

In the next installment in this series, we’ll take a step back and cover “iOS/Swift networking 101”, taking a closer look at the various objects used inside the WeatherGetter class.