January 10, 2019
(Full source code for this post: https://github.com/joshbuddha/Walk-Talk.git)
I have a music player in a current Swift project that is defined by data baked in a .plist file. In this post i’ll walk you through how I made the song data for the application editable on the server.
1. Define the JSON data and save the db.json in your project folder. I’m using json-server to simulate the rest api call. For more info on setting up json-server checkout https://github.com/typicode/json-server.
1 2 3 4 5 6 |
{ "track": [ { "id": "0", "trackOrder": "0", "artist": "MarcelinoZ", "song": "Waldport ThemeBoo","fileName": "mainTheme.mp3","vinylFile": "AtsaThemeSky","light": "0.0","windPower": "0.0","windDirection": "west"}, { "id": "1", "trackOrder": "1", "artist": "Cozy Photo", "song": "Old Land (Atsa Edit)","fileName": "OldLandAtsa.mp3","vinylFile": "cozyPhotoOldLand","light": "0.9","windPower": "0.1","windDirection": "east"}, { "id": "2", "trackOrder": "2", "artist": "Wild Year", "song": "The Failure of Sheep","fileName": "failureOfSleep.mp3","vinylFile": "wildYearFailure","light": "0.5","windPower": "0.2","windDirection": "north"} } |
2. Install Realm. It is already setup in the source files. If you want to set it up on your own add “pod ‘RealmSwift'” to your Podfile and run Pod install.
3. Create the DBLayer struct. The DBLayer will load the JSON with URLSession, decode the the JSON, and add each Track object to the Realm data store.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import Realm import RealmSwift struct DBLayer { mutating func loadJSON() { //make sure local url is available guard let url = URL(string: "http://localhost:3000/track") else { return } //load url with URLSession let task = URLSession.shared.dataTask(with: url) { (data, response, error) in guard let dataResponse = data, error == nil else { print(error?.localizedDescription ?? "Response Error") return } do { let decoder = JSONDecoder() //decode json to Track class with Decodable protocol let trackModel = try decoder.decode([Track].self, from: dataResponse) let realm = try! Realm() for track in trackModel { try! realm.write { //add the track classes with update set to true so you can easily call the server whenever needed. realm.add(track, update: true) } } } catch let parsingError { print("Error", parsingError) } } task.resume() } } |
4. Create the Track class so that the JSON can be decoded. They dynamic vars are setup to work with Realm and the Coding Keys are also setup in case we ever needed to redefine the property names.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import Realm import RealmSwift class Track: Object, Decodable { @objc dynamic var id: String? @objc dynamic var trackOrder: String? @objc dynamic var artist: String? @objc dynamic var song: String? @objc dynamic var fileName: String? @objc dynamic var vinylFile: String? @objc dynamic var light: String? @objc dynamic var windPower: String? @objc dynamic var windDirection: String? override static func primaryKey() -> String? { return "id" } private enum CodingKeys: String, CodingKey { case id case trackOrder case artist case song case fileName case vinylFile case light case windPower case windDirection } } |
5. Setup the ViewController to display the list of songs from the server. The main class variables the the DBLayer to make the network call, The Realm Results to retrieve the Track data, and a token for responding the database changes.
1 2 3 |
var myDB: DBLayer? var myTracks: Results<Track>? var token: NotificationToken? |
6. Within viewDidLoad is where you will setup the DBLayer, load the JSON, setup Realm to observe database changes, and initialize the myTracks array for the tableview display.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
override func viewDidLoad() { super.viewDidLoad() myDB = DBLayer() getDataFromServer() let realm = try! Realm() let results = realm.objects(Track.self) token = results.observe { _ in self.updateUI() } myTracks = realm.objects(Track.self) } |
This approach makes it easy to update the content of the music player. Also the UI will update anytime the database layer is changed. You can test this by changing the db.json file and hitting the update button to simulate a service call.
Here is the entire ViewController class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
// // ViewController.swift // WalkTalk // // Created by Josh Kneedler on 1/8/19. // Copyright © 2019 Josh Kneedler. All rights reserved. // import UIKit import Realm import RealmSwift class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tableView: UITableView! @IBAction func updateBtn(_ sender: Any) { getDataFromServer() } var myDB: DBLayer? var myTracks: Results<Track>? var token: NotificationToken? func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let tracks = myTracks else { return 0 } //print(tracks) return tracks.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "trackCell", for: indexPath) guard let songTitle = myTracks?[indexPath.row]["song"] as? String else { return cell } cell.textLabel?.text = songTitle return cell } override func viewDidLoad() { super.viewDidLoad() myDB = DBLayer() getDataFromServer() let realm = try! Realm() let results = realm.objects(Track.self) token = results.observe { _ in self.updateUI() } myTracks = realm.objects(Track.self) } func getDataFromServer() { myDB?.loadJSON() } func updateUI() { tableView.reloadData() } deinit { token?.invalidate() } } |
Once again all the code is here:
https://github.com/joshbuddha/Walk-Talk.git
Thanks for following along,
Josh
© 2023 jetstream
Recent Comments