This section completely updated to reflect changes in Xcode 6.3, as of April 16, 2015
In parts 1 through 5 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven’t read that yet, check out Part 1
If not, and you just want to start from here, download the code for Part 5 to get started. We’ll use it as a template to begin.
In this tutorial we’re going to do quite a few things, so let’s get started!
Modifying The API Controller code
First off, our actual plan for this app is to show iTunes music information. So let’s modify the API controller to better handle this information.
One thing’s been bothering me though. When we create our APIController, we set a delegate after it’s created. But, an API Controller without a delegate isn’t all that useful, so why even offer the option to make one?
Let’s add a constructor that accepts the delegate as it’s only argument.
init(delegate: APIControllerProtocol) { self.delegate = delegate }
Now, our delegate variable in the APIController isn’t actually going to be an optional any more. There is no APIController without a delegate!
So also change the delegate property to be an every day, non-optional APIControllerProtocol object.
var delegate: APIControllerProtocol
There’s also going to be an error at the end of the searchItunesFor method, because we’re treating the delegate object as an optional, but it’s not optional any more. So change the erroneous line to say this:
self.delegate.didReceiveAPIResults(results)
The only difference is we removed the ? from after the delegate property, to indicate it’s not an optional.
Now in our SearchResultsController, we need to change a few things. First, since the APIController constructor now needs the delegate object to be instantiated before *it* can be instantiated itself, we need to make it an implicitly unwrapped optional, and wait until viewDidLoad to assign it.
So in the api variable declaration change to this:
var api : APIController!
In the viewDidLoad method we need to unwrap the api object in order to call searchItunesFor(). You should end up with this
override func viewDidLoad() { super.viewDidLoad() api = APIController(delegate: self) api.searchItunesFor("JQ Software") }
Searching for Albums
Let’s also modify our call to the searchItunesFor() in the APIController to use a search term for music. We’ll also show a networkActivityIndicator, to tell the user a network operation is happening. This will show up on the top status bar of the phone.
override func viewDidLoad() { super.viewDidLoad() api = APIController(delegate: self) UIApplication.sharedApplication().networkActivityIndicatorVisible = true api.searchItunesFor("Beatles") }
Now in our urlPath in the APIController, let’s modify the API parameters to look specifically for albums.
let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"
We’ll now get results in the form of Album data, but this schema is a little different from apps. In fact running the app right now will we’ll just get default cells because the expected JSON data isn’t there. This is really fragile code, but we can make it slightly less fragile by doing some modeling of the data we expect.
Creating a Swift model for iTunes Albums
In order to facilitate passing around information about albums, we should create a model of what an album is exactly. Create a new swift file and call it Album.swift with the following contents:
import Foundation struct Album { let title: String let price: String let thumbnailImageURL: String let largeImageURL: String let itemURL: String let artistURL: String init(name: String, price: String, thumbnailImageURL: String, largeImageURL: String, itemURL: String, artistURL: String) { self.title = name self.price = price self.thumbnailImageURL = thumbnailImageURL self.largeImageURL = largeImageURL self.itemURL = itemURL self.artistURL = artistURL } }
It’s a pretty simple struct, it just holds a few properties about albums for us. We create the 6 different properties as strings, and add an initializer that sets all the properties based on our parameters.
So now we have a struct for albums, let’s use it!
Using our new Swift Album model
Back in our SearchResultsController, let’s modify the tableData array variable, and instead opt for a Swift native array for Albums. In swift, this is as easy as:
var albums = [Album]()
We can do away with do line var tableData = [], we won’t be using that any more.
This creates an empty array containing strictly Albums. We’ll now need to change our tableView dataSource and delegate methods to understand albums.
In the numberOfRowsInSection method, let’s change the number of items to the count of albums in our albums array:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return albums.count }
Now in cellForRowAtIndexPath, let’s swap out those dictionary lookups for a single album lookup:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as! UITableViewCell let album = self.albums[indexPath.row] // Get the formatted price string for display in the subtitle cell.detailTextLabel?.text = album.price // Update the textLabel text to use the title from the Album model cell.textLabel?.text = album.title // Start by setting the cell's image to a static file // Without this, we will end up without an image view! cell.imageView?.image = UIImage(named: "Blank52") let thumbnailURLString = album.thumbnailImageURL let thumbnailURL = NSURL(string: thumbnailURLString)! // If this image is already cached, don't re-download if let img = imageCache[thumbnailURLString] { cell.imageView?.image = img } else { // The image isn't cached, download the img data // We should perform this in a background thread let request: NSURLRequest = NSURLRequest(URL: thumbnailURL) let mainQueue = NSOperationQueue.mainQueue() NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in if error == nil { // Convert the downloaded data in to a UIImage object let image = UIImage(data: data) // Store the image in to our cache self.imageCache[thumbnailURLString] = image // Update the cell dispatch_async(dispatch_get_main_queue(), { if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) { cellToUpdate.imageView?.image = image } }) } else { println("Error: \(error.localizedDescription)") } }) } return cell }
Then there is the didSelectRowAtIndexPath method that needs to be modified to use the albums array. But, actually we’re not going to need this any more, so let’s just delete the whole method.
Creating Album objects from JSON
Now, all of this is not much use if we aren’t creating our album information in the first place. We need to modify our didReceiveAPIResults method of SearchResultsViewController to take album JSON results, create Album objects, and save them in to the albums array. Since we have a model for Albums now, it makes sense to move this functionality in to the Album model itself. So let’s make a minor adjustment to didReceiveAPIResults and delegate the responsibility of construction the albums array to the Album class.
func didReceiveAPIResults(results: NSArray) { dispatch_async(dispatch_get_main_queue(), { self.albums = Album.albumsWithJSON(results) self.appsTableView!.reloadData() UIApplication.sharedApplication().networkActivityIndicatorVisible = false }) }
Note that since this is where the api request comes to it’s conclusion, we also turn off the networkActivityIndicator.
Now in the Album.swift file we need to add a static method that creates a list of albums from a JSON list.
static func albumsWithJSON(results: NSArray) -> [Album] { // Create an empty array of Albums to append to from this list var albums = [Album]() // Store the results in our table data array if results.count>0 { // Sometimes iTunes returns a collection, not a track, so we check both for the 'name' for result in results { var name = result["trackName"] as? String if name == nil { name = result["collectionName"] as? String } // Sometimes price comes in as formattedPrice, sometimes as collectionPrice.. and sometimes it's a float instead of a string. Hooray! var price = result["formattedPrice"] as? String if price == nil { price = result["collectionPrice"] as? String if price == nil { var priceFloat: Float? = result["collectionPrice"] as? Float var nf: NSNumberFormatter = NSNumberFormatter() nf.maximumFractionDigits = 2 if priceFloat != nil { price = "$\(nf.stringFromNumber(priceFloat!)!)" } } } let thumbnailURL = result["artworkUrl60"] as? String ?? "" let imageURL = result["artworkUrl100"] as? String ?? "" let artistURL = result["artistViewUrl"] as? String ?? "" var itemURL = result["collectionViewUrl"] as? String if itemURL == nil { itemURL = result["trackViewUrl"] as? String } var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL, largeImageURL: imageURL, itemURL: itemURL!, artistURL: artistURL) albums.append(newAlbum) } } return albums }
I know this looks like a lot of new code, but actually there’s not much going on here. It’s really just looping through the list coming from allResults, and its grabbing values for some keys, and setting defaults if they’re missing.
The ?? operator used here is pretty neat. It works like this:
let finalVariable = possiblyNilVariable ?? "Definitely Not Nil Variable"
The finalVariable value is set to possiblyNilVariable if it is not nil. But if it is nil? It uses the value of the thing on the right-hand side of the ?? operator. In this case, the string “Definitely Not Nil Variable”.
We use this here in order to prevent getting nil values passed in to our Album.
On line 39, we create an Album object. On line 40 the album is added to the list.
Finally on line 43 the list of albums is returned.
If you run the app now you should see a new list of Album’s pop up. Cool, right?
Creating a second view
Now to actually show the details of an album, we’ll need a new view. First let’s create the class.
Add a new file called DetailsViewController.swift that inherits from UIViewController.
Our view controller will be pretty simple to start. We’re just going to add an album, and implement UIViewController’s init method as well as viewDidLoad().
import UIKit class DetailsViewController: UIViewController { var album: Album? required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func viewDidLoad() { super.viewDidLoad() } }
This code doesn’t do much yet, but that’s okay. We just need the class to exist in order to set up our storyboard.
Since we’ll be pushing views back and forth on the stack we’ll want a navigation bar as well. It’s hard to explain in text, and there is an NDA preventing me from showing parts of Xcode 6 in screenshots, so instead I created a short video demonstrating how to do this in Xcode 5. The process is nearly identical for Xcode 6 Beta, and is not under any sort of NDA.
In the video we did the following:
- Embedded our view controller in a navigation controller using the Xcode shortcut in the Editor menu, by clicking the view controller, then selecting Editor->Embed In->Navigation Controller
- Added a new view controller
- Set it’s class and storyboard ID to ‘DetailsViewController’
- Control+Clicked+Dragged from the table view cell in our first view controller to the new view controller we just created, and selected ‘show’ for the type of segue.
What this last step does is creates a segue on our navigation controller that pushes the new view on top of the stack. If you run the app now and click a cell, you should see this new view animate in.
Let’s build out a simple UI for this new view. It’ll contain a UIImageView that is 100×100 pixels, and a title Label. Drag these objects out of the object library and arrange them any way you like on the new view.
Providing the new view with Album information
When the storyboard segue fires off, it first calls a function on whatever view controller is currently on the screen called prepareForSegue. We’re going to intercept this call in order to tell our new view controller which album we’re looking at. Add the following in SearchResultsViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let detailsViewController: DetailsViewController = segue.destinationViewController as? DetailsViewController { var albumIndex = appsTableView!.indexPathForSelectedRow()!.row var selectedAlbum = self.albums[albumIndex] detailsViewController.album = selectedAlbum } }
What’s happening here is the segue parameter being passed in has a member called destinationViewController, which is our fancy new DetailsViewController we just created. In order to set the album member on it, we first need to cast it to DetailsViewController using the ‘as’ keyword as shown above.
Then, by using the indexPathForSelectedRow() method of our table view we can determine which album is selected at the moment this segue happens.
Using this information, well tell our detailsViewController which album was clicked before it is displayed.
Now I’m going to show you a pretty nifty feature of Xcode. We’re going to let it write some code for us.
Open up your storyboard again let’s start creating IBOutlets for our image view, label, button, and text view. On the top-right hand corner of Xcode there is the ‘assistant’ button. The icon looks like a bowtie and suit jacket. Clicking on this will open up a code window right next to your storyboard window. Make sure that one of the panels is showing DetailsViewController.swift, and the other is showing Main.storyboard.
Now, hold control, and click+drag from your image view to your code file. Just a line under your class definition for DetailsViewController. It’ll prompt you for a name, so let’s call it ‘albumCover’. The default options should be fine here. After doing this you should see this line of code newly added:
@IBOutlet weak var albumCover: UIImageView!
We just created a new IBOutlet, and now it’s connected to our storyboard’s DetailsViewController. How cool is that?
Do the same thing for the label you added to your view, and call it ‘titleLabel’.
Next, let’s modify viewDidLoad so that it will load in the info we’re being passed to our view objects, here’s the final DetailsViewController code:
import UIKit class DetailsViewController: UIViewController { var album: Album? @IBOutlet weak var albumCover: UIImageView! @IBOutlet weak var titleLabel: UILabel! required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func viewDidLoad() { super.viewDidLoad() titleLabel.text = self.album?.title albumCover.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL)!)!) } }
The @IBOutlets are the UI connections made by our storyboards, and our viewDidLoad method sets the title and album cover variables to load in from our Album object.
Now try running the app and taking a look. We can now drill in to details for albums and get a nice big detail view with the album cover and title. Because we pushed in a navigation controller, we also get a functional Back button for free!
If you made it this far, I want to personally congratulate you so let me know on twitter that you pulled it off! You are well on your way to creating real iOS apps with Swift.
I’ve decided this tutorial series is going to be expanded upon and refined, along with several other tutorials and essays on working with swift, Xcode, and Apple in a new book, which I have available for pre-order here. Also, I’ve decided to open up a new forum for all the followers of this tutorial.
Make sure to sign up to be notified of the new sessions.
The full source code for this section is available here.
In part 7, we set up a full Detail view with a working music player, and implement some great animations.
Awesome addition. Love the refactoring really helped clean things up.
Any plans for adding a tutorial using Core Data?
Yep, I have an unofficial list of what I’ll be adding. I haven’t decided exactly but I’d like to cover these topics:
Making views and presenting them programmatically
Drawing custom UIViews by overload drawRect
Using Core Data, NSUserDefaults, and file IO to store and retrieve data
Using CocoaPods to integrate with open source Obj-C and open source Swift libraries
Awesome looking forward to them!
Hi, awesome your tutorial.
One question: if I want to change the display type and instead of a list with the small picture inside, I would like to have big pictures (full width of the screen), with some text over the picture do I need to use something else or the listview is fine ?
Thanks.
Am loving this tutorial! Found tiny misspelling with ‘Receive’
self.delegate?.didRecieveAPIResults(jsonResult)
should be:
self.delegate?.didReceiveAPIResults(jsonResult)
Ignore my comment about ‘receive’. It is used consistently in the project. I had been retyping the code and had not noticed earlier rendering of this word.
Very sorry —
Oops! Search and replace to the rescue!
Loving the tutorials so far, can’t wait for the rest!
So on this line “var detailsViewController: DetailsViewController = segue.destinationViewController as DetailsViewController” I’m getting a “Swift dynamic cast failed” error. Any thoughts?
It probably can’t cast to your destination view controller because the storyboard has it set as a UIViewController. Make sure you changed the class in the Storyboard.
Hey,
great tutorial, I’m really looking forward to the rest.
On thing though, xCode tells me “Push segues are depreciated in iOS 8.0 and later”. Doesn’t matter now, I guess but maybe you want to address this at some point.
Thnx for doing these tutorials.
Looked at my project and noticed I’m not getting that error. It looks like I used a ‘Show’ segue instead. It seems to do the same thing.
Thanks for these tutorials, they’ve been great! I’ve got everything working, but the images only appear in the table view if
a) I tap on the row (I can either let it click through to the details page, or tap and hold to scroll)
b) I scroll until the row is off screen and then scroll back
I’ve put in debugging messages around the
if var albumArtsCell: UITableViewCell? = tableView.cellForRowAtIndexPath(indexPath) {
code and can see that albumArtsCell!.image = image is executing, so I can’t figure out why the image isn’t actually displaying…can you provide any help? Happy to post code or a video of the behavior if it’s helpful.
Scott
Make sure you set the image to a temporary image before letting it download. If you don’t the imageview itself is hidden until its clicked/reloaded.
So I have the line cell.image = UIImage(named: “Blank52”) right before the dispatch_async call. I also have a file called Blank52.png in the root of my project. Is there anything I need to do to ensure “Blank52” points to the file correctly? All I did was download that file from your github and then ‘Add files to “”‘ in Xcode.
Thanks!
Never mind, looks like I didn’t download the file properly so it wasn’t loading it as a temp image. I fixed the image file and it’s working now!
Jameson,
I needed this advice too. It worked and you should add it as a step to the tutorial.
Thanks, David
I’m getting “[_TtC8Bridging27detailsViewController _setViewDelegate:]: unrecognized selector sent to instance 0x10c678840”
when I click on any of the items in the list. Ideas? Trying to learn to use the breakpoint debugger, but its just sending me to the AppDeletgate, which isn’t terribly useful
I got the same error. My issue was that in the Main.storyboard -> Details View Controller -> View in the “Custom Class” I had typed DetailsViewController instead of leaving it blank as the default UIView. You need to add this custom class to the top component (not the view componet).
Hi, fantastic tutorial series. Bravo indeed.
Just a quick question:
Where you define:
let artistURL: String? = result[“artistViewUrl”] as? String
I’m getting a null reference for some reason, I isolated the error and tested it against:
if artistURL == nil { println(“Error”) }
And it prints the error.
Seems as if the data is getting packaged correctly for some reason.
Check out what’s inside the result variable and make sure the artistViewUrl key is there. It could be missing for a variety of reasons, but printing it to the console will help make that determination.
Hey, thanks for the reply. Check it out and you were right, printing to the console revealed the cause. Turns out that if you pass ‘Katy Perry’ as a search term, one of the albums that comes up is missing artist information as ‘result[“artistViewUrl”]’ prints ‘nil’ for that entry. For the search term ‘Coldplay’, this isn’t the case.
For safety, just before the creation of the new album, I added:
if name == nil || price == nil || thumbnailURL == nil || imageURL == nil || artistURL == nil || itemURL == nil {
continue
}
In order to prevent the problematic album from causing any harm. Alternatively, it would be easy to make the information optional instead of mandatory in order to get all available information.
Thanks again.
I had the same problem – iTunes seems to omit this field when there are more than one artist on the album (makes sense I guess).
I ended up setting to artistURL to “” if results[artistViewUrl] == nil
Yeah, that’s a better solution. Good job. 🙂
I’d love it if you could explain this syntax…
init(coder aDecoder: NSCoder!) {…}
We’ve seen formal parameter lists that have the form (variableName : Type). But not the additional word “coder” in there.
Lucian, this method comes from the Objective-C method initWithCoder:(NSCoder *)aDecoder {}.
In Obj-C ‘initWithCoder’ is the full method name, but in Swift the ‘Coder’ part is an external variable as opposed to being a part of the official method signature.
If you called this method directly you would just say
init(coder: myCoder)
As we’ve seen many times. Providing an external name makes the named parameters required.
If the method was defined as
init(coder: NSCoder!)
We would instead be calling
init(myCoder)
Without the named parameter.
The aDecoder part is included because this is a method definition that the Storyboard system calls when the view controller is unarchived. We’re not calling it in this example, we’re implementing it.
Or in other words, when calling this method, you pass in a coder variable, but from within the method, you use the aDecoder variable. It’s common for Obj-C methods to describe each parameter as part of the method signature, and in swift this is carried over by using external parameter names. Apple has some official documentation on this here:
http://bit.ly/UwbHjk
Thanks for the answer! I wonder why you chose to use an external name in this case? Would it have worked fine if you merely used an internal name?
(I guess you’re following convention — the base class uses an external name “coder”, so you figured you would do that as well).
I wonder why you chose the internal name to be “aDecoder” rather than picking “coder” for the internal name as well? You could have done that with
init(#coder : NSDecoder!) {…}
This is a method that the Storyboard system calls, I didn’t define it. Apple defines this method for UIViewController classes, and you have to adhere to it. If you create a blank UIViewController subclass, this method will automatically be added in fact.
That’s not the question I’m asking.
I’m asking why you picked the internal name “aDecoder”, when you could have more elegantly picked the internal name “coder” ?
I wonder why you made the “Album” constructor take non-optional strings, and hence used forced-unwrapping when constructing it?
var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL!, largeImageURL: imageURL!, itemURL: itemURL!, artistURL: artistURL!)
This fails for a bunch of artists where some of the fields are missing… e.g. try it with “Bach”. And there’s no point because you store the information inside the Album class in optional fields anyway.
I suggest to change the constructor to take optional parameters, and avoid the forced unwrap…
It was based on the assumption that all artists would have these fields. Since they don’t, these do in fact need to be optional.
I think it would be cleaner if you had in Album.swift:
init(json:NSDictionary) { …. }
In didReceiveApiResult, you can then just write:
albums = allResults.map { Album(json:$0) }
Album.swift knows how to convert json into Albums. The view controller doesn’t need to know anything about this. It just knows that there is a 1-1 mapping from element in the array to an actual album.
For anyone interested in this approach, this is the solution I came up with:
https://gist.github.com/frankieshakes/dc431d816e47e391f83e
And let me tell you… what a learning experience this was. Tons of fun too… really digging this series of tutorials.
Hey that’s great! Makes things much cleaner for sure 🙂
It seems like there is a big slow down in the load time between part 5 and part 6. Part 5 loads near instantly and part 6 is taking close to 20 seconds. Is it the conversion of the data into the Album type?
No, if you see a slowdown it may be due to not putting the UI updates on the main thread. Or more accurately, I may have made this mistake in part 6.
First of all, thinks for this awesome tutorials! They are valuable beyond measure!
Very looking forward to part 7 and your swift & iOS 8 book!
I think two things are missing in your SearchResultsViewController
1) if !cell {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: kCellIdentifier)
}
2) override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!){
// I named the segue identifier to “show” in storyboard
if segue.identifier == “show”{
var detailsViewController: DetailsViewController = segue.destinationViewController as DetailsViewController
var albumIndex = appTableView.indexPathForSelectedRow().row
var selectedAlbum = self.albums[albumIndex]
detailsViewController.album = selectedAlbum
}
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
// Get the row data for the selected row
self.performSegueWithIdentifier(“show”, sender: indexPath)
}
My code will only work with these two adding.
Are you sure the code you post in github still work?
The line self.albums = Album.albumsWithJSON(resultsArr)
is causing an error: ‘NSArray’ is not convertible to ‘Album’
Not sure what’s causing it, upgraded to beta4 today.
self.albums should be of type [Album]
Not Album
NSArray *is* convertible to [Album] since it’s just an array of albums.
Thanks again!
I was actually missing the class keyword for the albumsWithJSON declaration.
Great set of tutorials. Thanks!
Before I move on to #7 …
If the list has enough records to scroll, if I scroll around the album images become the Blank52 image and are slowly refreshed. Is that a “feature” of UITableView (I haven’t used one before) or a Beta 4 feature? Is that something we can control?
Also, you still have networkActivityIndicatorVisible = false in the code above. (It’s ok in GitHub.)
Thanks again.
It’s because of the unnecessary GCD method that was there before. It’s since been removed.
Sorry to be a pest, but which line was removed? Since it’s no longer in GitHub I cannot search for it there?
Line 61 and 95 here:
https://github.com/jquave/Swift-Tutorial/blob/7a38913130a18594cc6bdf56ad4e1b4efe59c58e/HelloWorld/SearchResultsViewController.swift
Hello,
Thanks for your tutoriel, it’s really nice !
Just ont thing, you need and you say to turn off the networkActivityStatus, but instead setting false, you set true. It’s just an error of inattention I think.
Thank you! Fixed.
I was following along nicely up to Tutorial 6 when I started getting the following error in Album.swift
“Fatal error: unexpectedly found nil while unwrapping an Optional value”
at this line “var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL!, largeImageURL: imageURL!, itemURL: itemURL!, artistURL: artistURL!, collectionId: collectionId!)”
I couldn’t fix it and downloaded the complete code for Tutorial 6 and I get exactly the same error.
Any ideas?
I found that if “artistURL = result[“artistViewUrl”] as? String” was nil then the program crashed.
I fixed it without any scientific reasoning with
var artistURL = result[“artistViewUrl”] as? String
if artistURL == nil { artistURL = “*”}
Obviously if the JSON doesn’t have an item for this then there is a problem, the program did not work searching for “Bob Dylan” but was OK for another artist.
BTW: Dumb question – should the tableview scroll? Mine doesn’t
I ran into a similar situation and decided to simulate what he had exemplified in the ‘price’ area.
I used the code below to get it working again (whenever artistViewUrl was nil):
var artistURL = result[“artistViewUrl”] as? String
if artistURL == nil {
artistURL = result[“collectionViewUrl”] as? String
}
I don’t know if you are still updating this with all the constant changes to the xCode beta but lazy is now defined as @lazy. Thanks for the great tutorials otherwise!!
I am still updating it. If you are seeing @lazy then you need to refresh 🙂
No, I was getting a compiler error while using “lazy” only, @lazy fixed it.
Any chance you know what is causing the UI to appear off the edge of the simulated screen? https://cloudup.com/c_GpWQKcd8I
In the details view also, what I centre in the storyboard isnt center in the simulator. Im using an iphone 5s sim and Ive set the storyboard size as suggested to wCompact hAny
Cheers,
Thanks for this awesome tutorial.
I’ve finished all of them. For this part, one problem I have is there are about two seconds delay to load the detail view. I’d like to know how to resolve this problem.
Make sure to put the table view reloading on the main thread.
Hey Jameson,
In Xcode 6.1.1, you would need to change one line of code, otherwise you’d get a compile error.
Line 26:
price = “$”+nf.stringFromNumber(priceFloat!)
Needs to be changed to:
price = “$”+nf.stringFromNumber(priceFloat!)!
Also for viewDidLoad, I had to make the following changes in order to run it:
Line 15:
albumCover.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL)))
Changed to:
albumCover.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL)!)!)