This section completely updated to reflect changes in Xcode 8.3.1, as of April 17, 2017
In part 1 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some text inside of them. If you haven’t read that yet, give it a read here.
For this section, we’re going to do something a little more ambitious. We’re going to hit the iTunes Search API for the iTunes Store, download the JSON results, parse them in to Dictionaries and then populate our Table View with this information. Then, we will add some user interaction by adding a click event to the tableview, so that when an item is clicked the iTunes store item will be opened.
If this sounds like a lot of work, don’t sweat it. This is pretty basic functionality for iOS apps and it’s one of the most common things any developer has to do. Let’s get going…
Connecting the UI
The first thing we need to do is get a reference to our tableView, so it can be used from code. Go ahead and add this line to your ViewController.swift file, just under the class definition, but outside of any functions.
@IBOutlet
var
appsTableView :
UITableView
!
This bit of code allows up to connect our Table View in our Storyboard to this variable, “appsTableView”. Save this file and open up your storyboard. Now select the View Controller object (the one with a yellow icon) and in the right-hand side pane click the last tab, the Connections Inspector. Here you should now see an outlet for “appsTableView”. Click and drag from the dot next to this outlet on to the Table View in our scene.
Let’s also add a variable to hold the table data itself. Just under the class definition for ViewController add:
var
tableData = [[
String
:
String
]]()
This variable is an Array
type that contains multiple Dictionary
types (or hashable types if you prefer). Inside these values the key is of type String
as well as the value. Or in other words I can get or set String
values by accessing this variable with any String
key in the dictionary, as we’ll see in a moment. If that does’t make sense just yet, just keep going and you’ll see how it’s used later in the tutorial.
Making the API Request
Now that we have the UI connected, we’re ready to make an API call. Create a new function called searchItunesFor(searchTerm: String). We’ll use this to make our requests happen for arbitrary search terms.
To keep this tutorial short, I’m going to just post my final code and let the comments do some of the explaining. I’ll also break it down line-by-line afterward. Also, I’m always open to questions and further discussion in the comments though, so feel free to chime in!
func
searchItunes(searchTerm:
String
) {
// The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
let
itunesSearchTerm = searchTerm.replacingOccurrences(of:
" "
, with:
"+"
, options: .caseInsensitive, range:
nil
)
// Also replace every character with a percent encoding
let
escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
// This is the URL that Apple offers for their search API
let
url =
URL
(string: urlString)!
URLSession
.shared.dataTask(with: url) { (data, response, error)
in
if
let
error = error {
// If there is an error in the web request, print it to the console
print
(error)
return
}
// Because we'll be calling a function that can throw an error
// we need to wrap the attempt inside of a do { } block
// and catch any error that comes back inside the catch block
do {
let
jsonResult = try
JSONSerialization
.jsonObject(with: data!, options: [])
as
! [
String
:
Any
]
self
.didReceive(searchResult: jsonResult)
}
catch
let
err {
print
(err.localizedDescription)
}
// Close the dataTask block, and call resume() in order to make it run immediately
}.resume()
}
First, we fix some issues with the search terms. For one thing, the iTunes API is not going to like seing spaces, instead it wants plus signs (+), so the replacingOccurences function does this work, and we put the result in the itunesSearchTerm variable.
Next, we also want to make sure no other characters will cause issues. For example a question mark (?) may be viewed by the API as the end of the request if it was included in the search term. So we use the method addingPercentEncoding
to encoded every character in to a percent-encoded form, and store this new value in to escapedSearchTerm.
After that we define our URL, which is just the iTunes search API URL with the escapedSearchTerm
inserted in to the term
field. We store this in the variable urlString
. Next, we convert this URL String
in to a proper URL
object. This will basically validate that the URL is valid, or at least properly-formed, and then it gives it some useful methods as well that are common to URLs.
Now comes the network request. We make the request from the URLSession
API that Apple provides. You could create a URLSession
for this task, but Apple’s API will provide a pre-constructed one called shared
. We access this with the simple dot-notation URLSession.shared
.
From here, we use the shared sesion to create a new Data Task by calling it’s dataTask
function with the url
we created the line before. The next part is the completion handler. This itself is a function we define inline, inside of the curly braces.
So, everything here inside of the curly braces, where the indentation is increased, is only executed after the Data Task has been completed. Hopefully when this happens, what we get back from the server is JSON data containing the search results! It’s also possible that there was some kind of error, so we check whether or not an error is present by using the optional binding syntax:
if
let
error = error {
...
This syntax replaces the optional error: Error?
with an unwrapped optional error: Error
, but only if it exists. Most of the time, the value of error
will be nil
, and this block of code will be skipped entirely.
Next we are going to decode the result from a Data
object in to a dictionary we can use to access each JSON element in the results.
Because the Apple APIs for JSON deserialization can throw
an error, we perform this work inside of a do { }
block, with a catch let err
block after it. If something goes wrong during JSON deserialization, the catch
block will be executed. But if everything goes to plan, the do
block will complete executing.
Once we get the deserialized data back, we call a new method self.didReceive(searchResult: jsonResult)
.
This method is not defined yet, so we’ll define it next.
Finally we call resume()
at the end of the dataTask block in order to execute the API request immediately.
Getting the data we need out of the JSON response
Now that we are getting a JSON response back, we will want to take out the values we care about. In this case that’s the price, a thumbnail, and the name of the app. So next, let’s create the didReceive
method inside our ViewController class in order to parse out this data and store it inside the tableData
variable that will inform our Table View.
func
didReceive(searchResult: [
String
:
Any
]) {
// Make sure the results are in the expected format of [String: Any]
guard
let
results = searchResult[
"results"
]
as
? [[
String
:
Any
]]
else
{
print
(
"Could not process search results..."
)
return
}
// Create a temporary place to add the new list of app details to
var
apps = [[
String
:
String
]]()
// Loop through all the results...
for
result
in
results {
// Check that we have String values for each key we care about
if
let
thumbnailURLString = result[
"artworkUrl100"
]
as
?
String
,
let
appName = result[
"trackName"
]
as
?
String
,
let
price = result[
"formattedPrice"
]
as
?
String
{
// All three data points are valid, add the record to the list
apps.append(
[
"thumbnailURLString"
: thumbnailURLString,
"appName"
: appName,
"price"
: price
]
)
}
}
// Update our tableData variable as a way to store
// the list of app's data we pulled out of the JSON
tableData = apps
// Refresh the table with the new data
DispatchQueue
.main.async {
self
.appsTableView.reloadData()
}
}
Here we are first off checking that we have the right data type. The results
argument passed in to the didReceive
function here is our JSON value returned from the iTunes Search API. We are going to want to check for three keys:
- artworkUrl100
- trackName
- formattedPrice
Why these keys? This is just the keys Apple chose in their API docs. If you take a look at a sample API response you’ll see keys contain the info we want about an app.
So what we do is we loop through everything inside of the array of results, and then check that each of these three values are present, and are convertible to a String
type. Here we’re using a compound form of the optional binding syntax that allows us to check for the valid presence of multiple values. if let varA = valueA, let varB = valueB { }
. Not only are we checking for their presence, but if they can be represented as String
objects.
If all three keys are present, and they can all be represented as strings, then we have an app record! We’ll add it to the list of apps, a temporary variable we created to store each new app we come across for this query.
I’m using slightly different keys for our app’s purposes. This is mainly just to show there is no reason these keys must match.
Finally, once the list of apps
is completely updated, we assign the tableData
variable that is going to be used to hold the Table View’s data to the array of apps
we just created. From there, we call reloadData()
on the appsTableView
, which let’s the table know there is new data to work with, and that it should re-render itself.
You’ll notice the reloadData()
is inside of another block. This one is from the Grand Central Dispatch API. By putting this code inside of the block DispatchQueue.main.async { }
, we’re able to assure this is executed on the foreground thread. Because this code is all being executed in response to a network request (the API call), we end up on a background thread. This is because we know we have to wait a moment for the API to respond. The iOS App won’t just freeze the app up while it waits for the response. Instead, it will send the response in to a background thread.
So, now that we are actually updating some UI, we want to jump back on to the main thread and make sure it shows up immediately. If you are curious, you can call reloadData
outside of this block and see how it effects the performance of the app (after we update the actual UI in the next step)
Update the UI with the new data
Now that we’ve got our API call updating the tableData
variable, and calling reloadData()
on the appsTableView
we connected earlier, we can proceed to actually implement those UITableViewDataSource
methods we set up in the beginning of the tutorial…
You may remember from last time we implemented the function for our Table View that we had a count method, which determines the number of rows; and a cell method, which actually creates the cell and modifies it for each row.
We’re going to update these now to use the data we pulled down from the web.
Swap out your methods with these two functions:
// MARK: UITableViewDataSource
func
tableView(_ tableView:
UITableView
, numberOfRowsInSection section:
Int
) ->
Int
{
return
tableData.count
}
func
tableView(_ tableView:
UITableView
, cellForRowAt indexPath:
IndexPath
) ->
UITableViewCell
{
let
cell =
UITableViewCell
(style: .subtitle, reuseIdentifier:
"MyTestCell"
)
// Get the app from the list at this row's index
let
app = tableData[indexPath.row]
// Pull out the relevant strings in the app record
cell.textLabel?.text = app[
"appName"
]
cell.detailTextLabel?.text = app[
"price"
]
return
cell
}
The first one is pretty simple. The number of rows should be the same as the count of the records in tableData
(the number of app returned from the API).
The second function is what creates the cell, which is what is displayed in the Table View. First we call the constructor to make a fresh UITableViewCell
to work with. Then we retrieve the current app
that should be displayed by accessing tableData[indexPath.row]
. The variable indexPath
is passed in as an argument by Apple’s Table View class. It represents the current index that it is asking for a cell for. It’s kind of a backwards way of thinking about it, but basically this function is the table itself asking for an individual row. Which row? Well… the one at indexPath.row
.
Next, we’ll set the text values for the text and detail labels of the cell. We’ll set these to be the value of the app
array we created earlier in response to the API response.
Call the API
As a final step, we’ll manually make the API call for our searchItunes
method by adding it inside of the viewDidLoad
function. The viewDidLoad
method is called on this View Controller object any time the screen it represents is being constructed. This is a common pattern in iOS development.
Find your viewDidLoad
function (the Xcode template will have already added it) and add a call to the searchItunes
function.
override
func
viewDidLoad() {
super
.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
searchItunes(searchTerm:
"JQ Software"
)
}
It’s alive!
Finally, run the app! Do you see the search results? You should see a few of my apps from the App Store pop up if you used my JQ Software search term. Try some other search terms and rebuild the app to see the different results that come back. You’ll notice we don’t yet have images in place, but we’ll get to that in later sections. We did get the data for that already after all.
If you run the App using JQ Software as a search term it will look like this at this stage:
Next time in Part 3 we’ll work on the interaction piece of the app, allowing users to search for anything they like, and making the table cells clickable!
Great to see a tutorial up so quickly, after nearly 3 years I consider my self to be a good iOS programmer, as I have not had much exposure to other languages one thing confuses me a bit in terms of structure in Swift.
In OjbC you have a header where anything public i.e. available to external classes, and then properties could either be in the .m or .h depending on if they were private or public.
I have played a bit with Swift, Am i right in understanding that all properties are now available to other classes unless declared inside a function. Also how do functions become private?
Regards Melvin
Melvin, thank you for your comments. The header files are the old tried and true way of determine what classes can see what, but in Swift we’re dealing with something quite different. It does in fact appear that the only way to make something private is to put it inside a function. This is not unlike javascript, and has become a common pattern to make ‘modules’ out of functions in order to create encapsulated systems.
Regarding actually making a function private, the easiest way is to use the module pattern. I’ve created a Gist for you here. Everything you see in this file is public, except the function named ‘someInnerFuncVar’.
https://gist.github.com/jquave/6fb62d951cebe4f6c7e5
I find this pattern somewhat confusing coming from a more purely object-oriented background. Your example shows how a single function might use a nested function to hide it, but how does a class maintain private state that needs to be accessible from multiple functions? Or let’s say that your nested function would be useful somewhere else in the class – how do you implement utility functions in a class (in a DRY way) that should never be called from outside the class?
In Obj-C, the de facto way to do this would be to add properties to the anonymous class continuation category in the .m file, effectively “hiding” them from the outside world, or to implement methods only in the source file and not declare them in the public header. There was also the option of creating a “protected” header for use by subclasses but not as a public interface.
Is there any possible way to do things like this in swift? If not, it seems like clean API design will be very difficult, and that open-source libraries will be full of “// DO NOT USE THESE FUNCTIONS”
I’m wondering why they didn’t just adopt the public/protected/private access model of c++ and similar languages. You still wouldn’t need a header, but it would be much easier to restrict access to properties and functions that nothing else has any business futzing with.
I read somewhere that private, protected, public etc. are coming to Swift in a near future. Apparently it was something that wasn’t ready. I believe I read this on some Apple forum where an Apple engineer replied.
Thanks Jameson, thats what I kind of figured, it does represent a new approach it seems. Looking forward to the rest of the series.
Hey. Thanks for these tutorials, I’m an Android developer with pretty much no experience with iOS, and your first couple of tutorials have been straight forward and make sense to an outsider like myself.
That’s great to hear! There are for sure some best practices that will need to be added in. I was planning to follow up with more interaction, but I may take a pit stop and start refactoring what we already have with proper code architecture principles. This code is not yet ready for production 🙂
No worries. I write a blog on Android development, and the code is always a proof of concept moreso than “I promise this won’t make your fellow developers want to strangle you”
Hi! Great tutorial!
I was playing with the code and run into some trouble.
if I change
“https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software”
for
“https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music”
I get an EXC_BAD_INSTRUCTION on
cell.text = rowData[“trackName”] as String
But if I println(rowData[“trackName”]) it prints fine on the console.
Any thoughts?
Thank you very much!
Try a println() on the JSON response before you try to access its members. Its possible the music results don’t always include a trackName. Normally I would add error checking to validate the key before using it, but like all of us I’m still fresh and it’ll probably come in part 3 🙂
When searching for software I’m getting a valid JSON response but am unfortunately also getting the same error as Guillermo. Any ideas?
I think the proper thing to do here is to add an optional check.
let cellText: String? = rowData[“trackName”]
if cellText? {
cell.text = cellText
}
I’m getting this same issue too. I tried the above fix but didn’t help.
My Xcode throws a breakpoint in at the `if jsonResult` line and when continuing to iterate through, the error I get in console is: fatal error: Can’t unwrap Optional.None
I haven’t changed the code you’ve provided except for the cellText lines above trying to fix this
Thanks for the tutorial! It’s been helpful, just too green to know how to debug this at this point
One additional note on my comment, it happens on the second item when iterating through, I get the above error and EXC_BAD_INSTUCTION as noted above
Yes I checked the JSON, and also tried this
cell.text = “foo”
Same error 🙁
I think there is probably something wrong with the cell. Try setting up a re-use identifier in a prototype cell in your storyboard. This is discussed more in depth in Part 4.
I’m receiving this error:
Search iTunes API at URL https://itunes.apple.com/search?term=JQ+Software&media=software
fatal error: Can’t unwrap Optional.None
(lldb)
I think part 4 covers this issue by addressing storyboard re-use identifiers of cells.
I received this error as well. On my end, ctrl+drag+dropping from the ‘Table View’ to the ‘View Controller’ did not connect “var appsTableView : UITableView” as my referencing outlet.
To Fix: I had to go back to the storyboard, ctrl+click on the “Table View” and then under “Referencing Outlets” select “appsTableView”;
Hope this helps. Not sure why it didn’t connect in the first place.
I’ve noticed a few quirks working with the new stuff, but it is beta after all. Maybe this stuff will get sorted out.
I’m experiencing the same error Weirdly, I don’t even see ‘appsTableView’ underneath ‘Referencing Outlets’ at all. Even though I have ‘@IBOutlet var appsTableView : UITableView’ in the ‘ViewController’ class.
Fixed by dragging the TableView from the Storyboard into the ViewController, whereby it created a new IBOutlet.
Curiously, the one it created had ‘ = nil’ on the end, not sure how important that is?
Either way, it’s now linked up and working. Great tutorial! 🙂
Not fixed to me. I can’t link it.
however, I have a lot of Xcode6Beta crashes while editing code. Have anyone the same issues?
Maybe I need to download again.
i was getting the same “fatal error: Can’t unwrap Optional.None”. As mentioned by others looks like this occurs because the outlet is not getting created when the drag/drop from table view to controller occurs. In order to fix mine, I opened up the assistant editor so that the storyboard file and the viewcontroller.swift file were visible side by side and I did the drag/drop from the tableview straight into the code file. All compiled and ran perfectly after that. Great tutorial so far, really learning a lot considering I have extremely limited Obj-C experience (coming from a .Net background). Looking forward to working thru the rest of the blog posts.
Just wanted to say well done on what is an easy to follow tutorial. Always good to have someone dive right in there and show how old things can be done anew! I’ll be singing your praises from the rooftop and see what I can do to create some exposure for your swift tutorials.
I’m looking forward to the next one.
Thank you very much!
Thanks for the quick tutorial. Will follow this for next episodes for sure.
I’m familar with Obj-C but Swift is way faster to code things.
atm I am totaly into Swift and trying to learn the ups and downs.
The only draw back is, it does not support OS X 10.8 or lower.
Also I believe it supports only iOS 7.0 and above, but time has a way of making that matter less and less.
Great job! Thank you very much!
Well, the first Swift code sample I have seen on the net. Already added your posts on my Swift learning page:
http://www.iphonetransferrecovery.com/Swift/free-ebooks.html
Excellent, thank you!
I followed along but where I ran into trouble was I made a data class and then in my View Controller I made a couple instances of that data class and tried storing those instances in an array so I could then fill my views rows by looping through the array.
I get an error when I try storing the instances in an array have you tried this yet or know if its possible in swift? Seems like when I create my instances then try to store them in the array it doesn’t know they exist yet.
Thanks for the great tutorial!
Can you post a Gist of your code? I would be happy to look at it and see what’s up.
Just got it to work! Thanks for the offer though.
I just broke my code up into a couple functions and then called them in the vieDidLoad and it seemed to take care of whatever the complier didn’t like.
Awesome!
Just for this quick and easy intro to Swift.
However I’m having performance issues. When querying for “Disney” I’m getting a tableview of about 30 rows or so. But when scrolling the performance is just terrible. Slow. Could it be because of the images?
Btw it happens both on my mac using simulator (iOS8) and on my iPhone 5 running iOS7
Yep you would want to ideally have caching of images and asynch loading. This demo intentionally cut all that out to keep things very simple. I’m working on the async right now for Part 5.
I think we can replace this part
// Download an NSData representation of the image at the URL
var imgData: NSData = NSData(contentsOfURL: imgURL)
cell.image = UIImage(data: imgData)
with another one that using threads or downloading images in the background. I think the previous code will wait till all images loaded first.
It will, it’s not an efficient way to do it. It’s something that’s replaced in the upcoming part 5.
I had a strange problem. I’m doing everything programmatically(i think that this way i can learn more) and my cellForRowAtIndexPath is not being called! ‘oO’
Even setting my delegates and datasource protocols and related methods, this method not get called… The strange thing is that my numberOfRowsInSection is called(first when we load the view and add the tableView as a subview, and than later, when i reload my tableview’s data once my request is finished). It is very strange… Here is a gist of my mainController that is being called as a rootVC in the window setted in my AppDelegate.swift :
https://gist.github.com/TiagoMaiaL/f95910d19d3ee1bebfc3
I used to make Lazy load in my setters(in fact, i made this in my swift code too), but i’m a bit confused, because now we have a keyWord called @lazy for this things.
Thanks!
Tiago, this means probably one of two things:
1) Your tabledata has 0 rows for some reason
2) Your method signature does not match the expected method signature for the table view delegate/datasource
But it’s probably #1
I thought that it was one of these erros.
After the request is finished, my array has 4 items. But when i call reload data, it does not call cellForRowAtIndexPath…
I had keep track of the states in this variables in each time. In relation to my method signature, it is correct, as i change that and the compiler shows me the error that my delegate is not implemented…
Thanks for the quick answer!
I have the same problem. I can see that results are coming back, but tableView(tableView: cellForRowAtIndexPath:) isn’t being called at all. I’ve done a copy/paste from your gist and all I’m getting is a blank table view.
The only reason that wouldn’t call is the delegate & dataSource of the tableview isnt set to self. In the storyboard file, click on the table view and go to the very last tab of the panes on the right-hand-side. It’ll show if delegate & dataSource are set.
> Now by control+click+dragging from the Table View to our ‘View Controller’ object, we’ve linked these objects. Easy, right?
You meant the opposite right? Something like “control+click+drag from our View Controller to our Table View” – that pops up a dialog to select the appTableView property
It works either way around.
Only worked for me dragging from ViewController object to TableView.
As a newbie to iOS programming, the fact that you can name multiple functions the same was odd to me. Maybe a quick explanation?
You can’t name multiple Obj-C functions the same, but the “name” is defined by the method signature. In objective-c if you have a method like this:
– (void) myMethodName:(int)myFirstParameter withASecondParameter:(int)mySecondParameter;
Then the method name in Swift is “myMethodName(…)”, so for this reason you may see what seems to be methods with the same names doing different things, but that’s because under the hood, the named parameters in Swift are part of the method signature in Obj-C:
“myMethodName: withASecondParameter:”
I had same issue. Only worked from View Controller to table View.
Would you mind posting about this at http://jamesonquave.com/forum/?
Hi,
first I want to thank you for this tutorials!!! Now the question. Why in connectionDidFinishLoading you use data instead of self.data?
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
Im a newbie in IOS 🙂
I don’t have a good reason, I’m surprised that works actually. I guess it knows to check for self.data
Thank you for putting the time into these tutorials, they have been a great help.
Hi,
I’m doing your tutorial and it is working. Now I’m trying to adapt it to my project.
Is there a simple way to parse a Json using swift ?
I have a json like:
{
“title”: “Train speed”,
“description”: “speed”,
“images”: [
{
“position”: 1,
“item”: [
{
“url”: “myurl_1”,
“width”: 640,
“height”: 359
},
{
“size”: “L”,
“url”: “myurl_2”,
“width”: 320,
“height”: 179
},
{
“size”: “M”,
“url”: “myurl_3”,
“width”: 160,
“height”: 89
},
{
“size”: “M”,
“url”: “myurl_4”,
“width”: 160,
“height”: 89
}
]
}
]
}
For the moment I managed to parse it using loop inside loop and loop again.
Here is what I did:
if let element = rowData[“images”]{
var rowDataImg: NSDictionary = element[0] as NSDictionary
if let element1 = rowDataImg[“item”]{
var rowImgUrl: NSDictionary = element1[0] as NSDictionary
var urlString: NSString = rowImgUrl[“url”] as String
var imgURL: NSURL = NSURL(string: urlString)
var imgData: NSData = NSData(contentsOfURL: imgURL)
cell.image = UIImage(data: imgData)
}
}
Thanks.
JM.
var deserializedJSON: NSDictionary = NSJSONSerialization.JSONObjectWithData([myJsonString.dataUsingEncoding(NSUTF8StringEncoding)], options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
Thanks for the answer.
I’m a beginner in iOs App (I’m a java developper actually) .
Can you please show me how to get the value (for instance) of the “url” attribute? Can I use something like a xpath ?
I’m used to groovy where you just do something like deserializedJSON.att1.att2.att3 ..
Thanks.
Try this:
var images: NSArray = deserializedJSON[“images”] as NSArray
var items: NSArray = images[“item”] as NSArray
var firstItem: NSDictionary = items[0] as NSDictionary
var url: String = firstItem[“url”] as String
The main issue here is that the JSON is being deserialized in to Obj-C types, NSDictionary and NSArray. If it were a pure Swift object it would be much better, but that API doesn’t seem to exist yet. Maybe I’ll write it.
I tried but it does not work.
It fails at:
var items: NSArray = images[“items”] as NSArray
with the message: Cannot convert the expression’s type NSArray to type StringLiteralConvertible.
Any idea?
I’ve tried some stuff but I cannot get the thing working.
Thanks.
I am having a very similar problem, getting these two errors:
var rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary <-'NSArray' is not convertible to 'NSDictionary'
and
self.tableData = results <- 'AnyObject' is not identical to 'NSArray'
Any ideas as to why?
Hi Tom, there were some issues caused by Xcode updates that have been fixed (just now) up until Part 4. Sorry about that.
Hi Jameson,
I have tried with tutorial which you have sent, but one problem i’m facing is “fatal error: Can’t unwrap Optional.None”, this error is raising on the self.appsTableView.reloadData()……
Please help me regarding this to move forward
TAI
Jawith, make sure you have your appsTableView set up as an IBOutlet, and that it’s connected in your storyboard to the Table View.
This is a great tutorial and I am really enjoying it however, I noticed that you’re not using the baked in inference that helps make this language so light. Any particular reason?
I start using it more later in the series. The only reason I’m doing it in the beginning is because I’m used to it. I would go back and remove it, but I also think it helps explain what’s happening under the hood a bit.
Is part 2 in github by chance? I’m getting an error in connectionDidFinishLoading but am also noticing that connection(didRecieveResponse) and connection(didRecieveData) are not being triggered. URL seems good so my delegate connection must be broken somewhere.
Thanks!
Phong
The github commits start at part 3: https://github.com/jquave/Swift-Tutorial/tree/Part3
But, you can find the offending line and compare with your own. One thing to note is that the delegate/protocol pattern is dropped later in this tutorial in favor of a different networking method that uses callbacks.
Minor nit, but it may also have an unexpected consequence.
You are systematically misspelling “receive” as “recieve,” except in the one case where it really matters. Normally, I wouldn’t care, but you have a callback function:
func connection(connection: NSURLConnection,
didRecieveResponse response: NSURLResponse) {
println(“Recieved response”)
}
Is this function just never going to be called, because it is misnamed? What will happen if the system really does want to call this method and it finds this misnamed method? Is it just ignored?
Yes it would be ignored. That callback method is optional, so it’ll just be skipped. I corrected the misspellings in the tutorial.
Hey great tutorial!
There’s one line in your searchItunesFor function that puzzles me:
var results: NSArray = jsonResult[“results”] as NSArray
I’m wondering: where did you get the argument name [“results”] ? Meaning how did you know to use that name and not some other name? And are there other names or values that can be used there?
thanks!
actually, I can answer my own question here 🙂
“results” comes directly from the iTunes Search API itself: that’s the word they use for returned JSON results. So it comes from Apple. All I had to do was look that up…
thanks anyway – keep up the good work!
Yep, usually I just println(whateverJSONIGetBack) and take a look at what I want to include.
thanks for this tute
I’m trying to use this code as a base for accessing another JSON API, but whenever I change the URL and try to run it I get an EXC_BAD_INSTRUCTION error on the line ” println(error.localizedDescription)” If I try to comment out that line, I get errors pretty much all the way up. Would this code only work for this particular API or am I just doing something wrong?
It should work the same with any API, I’m going to say there’s probably a different issue. I’m guessing you need to modify the way it’s deserialized a bit due to a difference in the way the data is structured.
Good,Of great help to me.Thank you!Jameson Quave
Great Tutorial!! I do have one question though. How would I go about connecting to a url that required username/password authentication? Thanks!
i see in beta 3 cell.text is deprecated what is the correct replacement
answered my own question…
should be using
cell.textLabel.text
Doesn’t compile on xCode 6 Beta 3.
cell.text -> cell.textLabel.text
Not sure what to do with cell.image though – I find it difficult to see what to do with deprecations, in java we usually get a comment with a clue as to what has replaced the deprecation
Just left a comment, but change:
cell.image = UIImage(data: imgData)
to
cell.imageView.image = UIImage(data: imgData)
In the latest Xcode6-Beta3, you won’t be able to run your program (due to deprecation errors) until you change the following:
From:
cell.image = UIImage(data: imgData)
To:
cell.imageView.image = UIImage(data: imgData)
AND
From:
cell.text = rowData[“trackName”] as String
To:
cell.textLabel.text = rowData[“trackName”] as String
Hey, I’m new to Swift and trying to follow your tutorial. I’m using xcode 6 – beta 5 and im getting a compile error for the line after jsonResult
if (err?)
Type NSError? does not conform to protocol ‘BooleanType.Protocol
and in the console
fatal error: unexpectedly found nil while unwrapping an Optional value
This is likely an error on my part, just wondering if anybody else could reproduce it. Thanks for the tutorial
Hi Tyler, the line that says if(err?) needs to change.
Now it should say:
if(err != nil)
The tutorial has now been completely updated to support Beta 5 since you posted this message 🙂
Works perfectly. Thanks for the quick turnaround. Really appreciate it
My build is apparently successful but my table is blank. I checked that my view controller is connected to my delegate and datasource. Any idea what Could be going on here? Would have anything to do with me using Xcode Beta 2?
The issue is possibly your Xcode version. I would at least upgrade to Beta 5 to check.
I’m getting this error: The operation couldn’t be completed. (NSURLErrorDomain error -1002.)
It’s getting thrown here:
if((error) != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
I’m using Xcode 6 beta 6
This was caused by beta 6 indeed, the tutorial has now been updated and this issue is resolved 🙂
Thanks for making these guides!
I ran into some troubles running the code :
“Task completed
The operation couldn’t be completed. (NSURLErrorDomain error -1002.)
fatal error: unexpectedly found nil while unwrapping an Optional value”
failing on line :
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
Im using beta6
This issue is caused by a change to optionals in beta 6. I’ve updated my tutorial since you posted this, so it should all now work.
thank you!! Could I ask for a more detailed explanation of the cause/solution by any chance? 🙂
Just upgraded to Xcode 6 beta 6. When I run this tutorial (part 2) as described above, I get “The operation couldn’t be completed. (NSURLErrorDomain error -1002.)
fatal error: unexpectedly found nil while unwrapping an Optional value”
If I modify the URLPath and put something in like:
let urlPath = “https://itunes.apple.com/search?term=apple&media=software”
Then it works fine. Is it possible that beta 6 might have changed something and now your original code is returning an empty set from iTunes?
All the xcode beta (breaking) changes almost make noobs like myself want to wait until it’s finally released…almost… 🙂
Heya! Yeah they made some more breaking changes in beta 6, I’ve updated my tutorial since then so it should all now be up to date 🙂
I see above that you fixed the code for Beta6, but I am running beta 6 (6A280e) and I keep getting…
The operation couldn’t be completed. (NSURLErrorDomain error -1005.)
fatal error: unexpectedly found nil while unwrapping an Optional value
at…
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
I even checked out your github repo to verify it wasn’t my coding mistake.
Ah, needed to make it “https://”. That fixed it.
You mention a method didReceiveAPIResults a couple times, but that is not in the code samples.
Not sure why this isn’t included, but you can see the code here: https://github.com/jquave/Swift-Tutorial/blob/Part3/MusicPlayer/APIController.swift
hi
the swift is a compilery language or a Interpreter language?
Compiled of course 🙂
It’s rather counterproductive to create a comprehensive tutorial for a new programming language if three months in you stop updating the code. Please keep your material up to date and accurate or remove it so people can learn accurate information.
I update these as frequently as I can, I’ve been working on this for the past week, this page in particular was updated a few days ago.
i have questions:
1)why swift is a compilery language when it runs the same time?
2)when we can exchange the data o fvariables this is correct?
var name = “hello”
name = 20 //exchange string to int
Jameson Quave you can write the txt of the Conference that intruduction of swift?
whit link:http:
//www.bing.com/videos/search?q=swift%20programming%20language%2Byoutube&qs=n&form=QBVR&pq=swift%20programming%20language%2Byoutube&sc=0-27&sp=-1&sk=#view=detail&mid=728F416521E939E080A1728F416521E939E080A1
i dont want that link.
excuse
My network was behind a proxy. After changing to a network without proxy, everything worked ok!
Hey, thanks for the tutorial. however i needed to add a question mark at the end of these two in order for it to work:
cell.textLabel?.text = rowData[“trackName”] as? String
cell.imageView?.image = UIImage(data: imgData!)
Whether or not these properties are optional (and therefore whether or not they need the question mark) has been changing with every version of Xcode, so it’s slightly different for everyone. However, Xcode will automatically suggest fixes to these issues.
In either event, you should learn why this is happening by reading up on Optionals in Swift and how they work.
Hi,
thanks for your great work here. I’m new to Swift (and iOS programming).
I’m trying to run the code example above and I’m constantly getting the same mistake (even if I download your code from GitHub) – I’m using Xcode 6.1.1
In ViewController.swift
cell.textLabel.text = rowData[“trackName”] as? String
Error: UILabel? does not have a member named ‘text’
(…)
cell.imageView.image = UIImage(data: imgData!)
Error: UIImageView? does not have a member named ‘image’
Any idea how I could solve this?
Thanks
could solve it by myself:
change
cell.textLabel.text = rowData[“trackName”] as? String
to
cell.textLabel!.text = rowData[“trackName”] as? String
AND
cell.imageView.image = UIImage(data: imgData!)
to
cell.imageView!.image = UIImage(data: imgData!)
I’m also new to all of this and I just changed it to:
cell.textLabel?.text = rowData[“trackName”] as? String
cell.imageView?.image = UIImage(data: imgData!)
I’m not sure what the difference between a “?” and “!” is, but they both seem to get the job done.
Using ? is safer, if the textLabel or imageView don’t exist, the code is not run.
However, if you use !, you are *insisting* that they exist, and if they don’t, the app will crash.
Hi,
Great tutorial man, just fix the “or” to “for” under the line:
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
and keep going with your great work
Thanks for that 🙂
I am getting this error:
“extra argument ‘error’ in call” at this line:
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
the problem seems to be “error: &err”
I am using Xcode 7beta
Xcode 7 Beta uses a new Error handling paradigm. Here’s the short version of how to fix cases like this:
1) Delete the error argument, and any references to the err variable.
2) Put try! in front of the call that used to have the error argument (this is unsafe, but will help you get through this for now)
e.g.
let jsonResult = try! NSJSONSerialization.JSONObjectWithData(data!, options: nil) as! NSDictionary {
Hello,
thanks for the answer.
But using “try” why are setting “options” to “nil”?
Thanks
Oh you don’t need to set them to nil, we aren’t really interested in the differences in the option for this tutorial. The options basically just control whether or not you can modify values after retrieving them
Doing this way build is ok
if let jsonResult = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableLeaves) as? NSDictionary {
if let results: NSArray = jsonResult[“results”] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.tableData = results
self.appsTableView!.reloadData()
})
}
}
but I get an error when launching the app
2015-06-14 13:04:28.066 iTunesSearch[1578:295858] CFNetwork SSLHandshake failed (-9824)
2015-06-14 13:04:28.068 iTunesSearch[1578:295858] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Task completed
An SSL error has occurred and a secure connection to the server cannot be made.
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Hi, thanks for the tutorial, it’s very interesting so far. I just ran into something that may be due to newer changes, perhaps? I’m not sure. Copying and pasting in the code above for tableView results in XCode giving some errors and it won’t compile:
For the lines:
if let rowData: NSDictionary = self.tableData[indexPath.row] as? NSDictionary,
// Grab the artworkUrl60 key to get an image URL for the app’s thumbnail
It’s complaining: “Expected ‘{‘ after ‘if’ condition”.
And then a few lines further down for:
trackName = rowData[“trackName”] as? String {
It’s complaining: “Braced block of statements is an unused closure”.
Being new to Swift I’m not sure yet how to address these but I’ll dig into it. Any suggestions would be great. Thanks for putting the work into this for us all!
Okay, never mind my comment above. I had thought I was running the latest XCode but in fact it was a little behind. I updated and everything is working now — thanks again, sorry for the false alarm!
What version of Xcode?
Same issue for me –> Xcode Version 6.2
It was time to update it 🙂
Hi, I think the code dosent work for XCode 7.3, would be nice it you could give some hints in fixing the problems, im new to IOS, would be nice.
Thanks Rares, I will be updating this tutorial with Swift 3 syntax after WWDC this coming week. Join up on the mailing list and you’ll get a notification about it.