Please look at the screen recording below to know what the finished project should look like:
Please fork and clone this repository. This repository does not have a starter project but, so create one inside of the cloned repository folder. This repository does contain a file with example JSON that you will need in order to set up your model objects.
- Remove the view controller scene the Main.storyboard comes with.
- Add a table view controller scene and embed it in a navigation controller. Set the navigation controller as the initial view controller.
- Create a Cocoa Touch subclass of
UITableViewController
calledAlbumsTableViewController
and set this table view controller's class to it. - Add a bar button item to the table view controller's navigation item. Change its system item to "Add".
- Set the prototype cell's style to "Subtitle", and give the cell a reuse identifier.
- Add a second table view controller scene. Create a show segue from the first table view controller's cell, and another show segue from its bar button item.
- Add a navigation item to this view controller scene. Then add a bar button item and set its system item to "Save".
- Add a
UIView
as the detail table view's header view. In this header view, add 4 text fields. The text field's placeholder text should show to the user that they must supply:- The album's name.
- The artist.
- The genres (separated by commas).
- URLs to the cover art (separated by commas).
- Create a Cocoa Touch subclass of
UITableViewController
calledAlbumDetailTableViewController
and set this table view controller's class to it. Create outlets from the four text fields, and an action from the bar button item. - Each cell will represent a song. In the prototype cell, add two text fields. The first should take in the song's title, and the second should take in the duration of the song.
- Add a button in the cell also. This will add the song to the album. Set its title to "Add Song".
- Create a Cocoa Touch subclass of
UITableViewCell
calledSongTableViewCell
and set this cell's class to it. Create outlets from the text fields and the button. Also create an action from the button.
The whole purpose of this project is to help you understand how Codable
works under the hood when it automatically synthesizes conformance to the protocol, as well as giving you the ability to implement it yourself when necessary.
- Create a new Swift file called "Album.swift". Create a model object called
Album
. - Using the JSON provided in the "ExampleAlbum.json" file, parse the JSON to figure out what properties your
Album
model should have.- NOTE: Create a second model object called
Song
for the array of Songs. YourSong
should have 3 properties. - NOTE: You must have a property for every value the JSON contains. There should be 6 properties.
- NOTE: Create a second model object called
- Adopt just
Decodable
on both model objects for now. Starting withSong
, implement the requiredinit(from decoder: Decoder) throws
initializer.- The goal of implementing this initializer yourself is to avoid using nested structs, and keeping your model object "flat".
- Use the example JSON to figure out how to decode it into your model objects.
- Assume that all properties in the model objects are not optional and are variables.
- Add the "exampleAlbum.json" file to your project. Make sure you check "Copy file(s) if needed", and add it to your target.
- Create a new Swift file called "AlbumController.swift". Create an
AlbumController
class. - Create a function in the
AlbumController
calledtestDecodingExampleAlbum()
. This should:- Load the resource from your App bundle:
let urlPath = Bundle.main.url(forResource: "exampleAlbum", withExtension: "json")
- Get the JSON data from the "exampleAlbum.json" file. (
Data(contentsOf: URL)
) - Try to decode the JSON using JSONDecoder just like you would if you got this data from an API.
- Check for errors. This is important because it will help you make sure you've correctly implemented the
init(from decoder: Decoder) throws
initializer in your model objects by giving you an error about what you have potentially done wrong.
- Load the resource from your App bundle:
- Run this function in the
AppDelegate
. Make sure you don't get any errors when decoding the example JSON before you move on. - Back in the "Album.swift" file, now adopt
Codable
in both model objects. - Implement the
encode(to encoder: Encoder) throws
function. This function should encode the JSON back into its original nested state (i.e. the encoded JSON should match the structure of the example JSON exactly). - Create a function in the
AlbumController
calledtestEncodingExampleAlbum()
. Copy and paste the code from thetestDecodingExampleAlbum()
method. Then simply try encoding the newly decodedAlbum
. Again, check for errors to make sure you're encoding correctly.
Now you will add the functionality to fetch Albums from and send them to an API. In the AlbumController
, create and the following:
- An
albums: [Album]
variable that will be the data source for the application - A
baseURL: URL
property. Create or use an existing Firebase Database for the base URL. - A function called
getAlbums
. It should have a completion handler that takes in an optionalError
. This function should perform aURLSessionDataTask
that fetches the albums from thebaseURL
, decodes them, and sets thealbums
array to the decoded albums. Note: You should decode the JSON data as[String: Album].self
here. - A function called
put(album: Album)
. This should use aURLSessionDataTask
to PUT the album passed into the function to the API. Add the album's identifier to the base URL so it gets put in a unique location in the API. - A function called
createAlbum
. It should take in the necessary properties as parameters in order to initialize a newAlbum
. Create anAlbum
from the method parameters, then append it to thealbums
array. Then call theput(album: Album)
method so the newAlbum
gets saved to the API. - A function called
createSong
. It should take in the necessary properties as parameters to be able to initialize aSong
. The function should return aSong
. In the method, simply initialize a new song from the method parameters and return it. - A function called
update
. This should take in anAlbum
and a parameter for each of theAlbum
object's properties that can be changed (This should be every property). Update the values of theAlbum
parameter, then send those changes to the API by calling theput(album: Album)
method.
Test the createAlbum
method by either using the example JSON or passing in your own Album
information. Make sure it gets sent to the API, and in the correct structure.
In the AlbumsTableViewController
:
- Create an
albumController: AlbumController?
variable. - In the
viewDidLoad
, call thegetAlbums
method of thealbumController
. Reload the table view in its completion closure. - Implement the required
UITableViewDataSource
methods. The table view should display the albums in thealbumController
'salbums
array. The cells should show the album's name and artist. - Go to the
AlbumDetailTableViewController
. Add the following:- An
albumController: AlbumController?
variable. - An
album: Album?
variable.
- An
- Back in the
AlbumsTableViewController
, implement theprepare(for segue: ...)
method. If the segue is triggered from the bar button item, it should pass thealbumController
. If it's triggered from tapping a cell, it should pass thealbumController
and theAlbum
that corresponds to the cell.
In the SongTableViewCell
:
- Create a
song: Song?
variable. - Create an
updateViews
method. It should:- Check if the song exists. If it does, set the text fields' text to the corresponding values of the
Song
. - If the song exists, also hide the button.
- Check if the song exists. If it does, set the text fields' text to the corresponding values of the
- Implement the
prepareForReuse()
method. Clear the text fields' text, and unhide the button. - Create a class protocol above or below the
SongTableViewCell
class calledSongTableViewCellDelegate
. It should have a single function:func addSong(with title: String, duration: String)
. - Create a
weak var delegate: SongTableViewCellDelegate?
. - In the action of the bar button item, call
delegate?.addSong(with title: ...)
. Pass in the unwrapped text from the text fields for the parameters to the method.
In the AlbumDetailTableViewController
:
- Create a
tempSongs: [Song] = []
array. This will be used to temporarily hold the songs the user adds until they tap the Save bar button item to save the album (or changes to it). - Create an
updateViews
method. It should- Take the appropriate values from the
album
(if it isn't nil) and place them in the corresponding text fields. You can use the.joined(separator: ...)
method to combine the urls and genres into strings. - Set the title of the view controller to the album's name or "New Album" if the album is nil.
- Set the
tempSongs
array to the album's array ofSongs
.
- Take the appropriate values from the
- Call
updateViews()
in thedidSet
property observer of thealbum
variable, and in theviewDidLoad()
. Remember to make sure the view is loaded before trying to set the values of the outlets or the app will crash. - Adopt the
SongTableViewCellDelegate
protocol. - Add the
addSong
method from the delegate you just adopted. In it:- Create a
Song
using thecreateSong
method in thealbumController
. - Append the song to the
tempSongs
array - Reload the table view
- Call
tableView.scrollToRow(at: IndexPath, ...)
method. You will need to manually create anIndexPath
. Use 0 for the section and thecount
of thetempSongs
for the row.
- Create a
- Implement the
numberOfRowsInSection
method using thetempSongs
array. Return the amount of items in the array plus one. This will allow there to be an empty cell for the user to add a new song to. - Implement the
cellForRowAt
method. Set this table view controller as the cell'sdelegate
. - Implement the
heightForRowAt
method. Set the cell's height to something that looks good. Account for the cells whose buttons will be hidden, and the last cell whose button should be unhidden. In the screen recording, the hidden button cells' height is 100, and the last cell's height is 140. - Finally, in the action of the "Save" bar button item:
- Using optional binding, unwrap the text from the text fields.
- If there is an album, call the
update(album: ...)
method, if not, call thecreateAlbum
method using the unwrapped text, and thetempSongs
array. - Pop the view controller from the navigation controller.
- Add a method to fetch the image(s) from an album. Add a collection view either directly to the
AlbumDetailTableViewController
or in a container view in it that shows the album cover(s). - Add the ability to search for albums and songs using an external API such as the iTunes Search API. (Note: the iTunes Search API does not include song durations, so you may have to include a default value for the duration.