Have some problem with loading data from JSON to my labels in table view cell. Data is working, I can print it, but when I trying to load, it shows an error "Index out of range" or only empty table view. Here is my model and function to load JSON in view controller:
import Foundation
import UIKit
struct BeatData: Decodable {
let data: [BeatPackData]
}
struct BeatPackData: Decodable {
let loops: [Loop]
let beatloops: [BeatLoop]
}
struct BeatLoop: Decodable {
let name: String
let instrument: String
let songName: String
let producer: String
}
struct Loop: Decodable {
let name: String
let producer: String
let count: String
let genre: String
}
public class DataLoader {
@Published var beatLoops = [BeatLoop]()
init() {
parseJSON()
}
// loadLoops()
// }
//
func parseJSON() {
guard let path = Bundle.main.path(forResource: "data", ofType: "json") else {
print("n-------> bundle path error")
return
}
let url = URL(fileURLWithPath: path)
do {
let jsonData = try Data(contentsOf: url)
let response = try JSONDecoder().decode(BeatData.self, from: jsonData)
self.brainLoops = response.data.beatloops
for beatPackData in response.data {
self.beatLoops.append(contentsOf: beatPackData.beatloops)
}
print("n-------> response: (response)")
}
catch {
print("n====> error: (error)" )
}
return
}
}
}
On the top of view controller, I’ve created an instance: let dataNew = DataLoader()
Here is my table view methods:
extension BeatPackViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataNew.beatLoops.count
// return dataNew.beatLoops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomLoopsCell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
// gettingSongName()
cell.loopNameLabel.text = dataNew.beatLoops[indexPath.row].name
cell.producerLabel.text = dataNew.beatLoops[indexPath.row].producer
cell.instrumentLabel.text = dataNew.beatLoops[indexPath.row].instrument
cell.delegate = self
cell.selectionStyle = .none
cell.tag = indexPath.row
// cell.playButtonOutlet.tag = indexPath.row
if let playingCell = currentPlayingIndex, playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
return cell
}
Also here is my JSON file:
{
"data": [
{
"beatloops": [
{
"name" : "Alien",
"instrument" :"Arp",
"songName" : "alienarpjason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Big Brake",
"instrument" :"Drums",
"songName" : "BigBrake_Drums_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Bongo Beats",
"instrument" :"Drums",
"songName" : "BongoBeats_Drums_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Dreaming",
"instrument" :"Keys",
"songName" : "Dreaming_Keys_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Funky Groove",
"instrument" :"Bass",
"songName" : "FunkyGroove_Bass_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Futurist",
"instrument" :"Arp",
"songName" : "Futurist_Arp_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Hoping for change",
"instrument" :"Arp",
"songName" : "HopingForChange_Arp_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Manic",
"instrument" :"Bass",
"songName" : "Manic_Bass_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Sassy",
"instrument" :"Drums",
"songName" : "Sassy_Drums_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Serious",
"instrument" :"Arp",
"songName" : "Serious_Arp_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Stable Bricks",
"instrument" :"Bass",
"songName" : "StableBrick_Bass_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Thump",
"instrument" :"Drums",
"songName" : "Thump_Drums_Jason.mp3",
"producer" : "Stefan Guy"
},
{
"name" : "Tropic",
"instrument" :"Drums",
"songName" : "TropicVibe_Drums_Jason.mp3",
"producer" : "Stefan Guy"
}
],
"loops": [
{
"name": "Away we go",
"producer": "Tubular Kingz",
"count": "28",
"genre": "Lo-fi Hip Hop"
},
{
"name": "Test",
"producer": "Testing",
"count": "25",
"genre": "Lo-fi"
}
],
}
]
}
My view controller:
import UIKit
import AVFoundation
class BeatPackViewController: UIViewController, UIGestureRecognizerDelegate, UINavigationControllerDelegate {
@IBOutlet weak var beatView: UIView!
@IBOutlet weak var beatTableView: UITableView!
@IBOutlet weak var coverImage: UIImageView!
@IBOutlet weak var looppackNameLabel: UILabel!
@IBOutlet weak var producerNameLabel: UILabel!
@IBOutlet weak var backButtonLabel: UIButton!
var allButtons: [UIButton] = []
var currentPlayingIndex : Int?
// let data = [BeatData]()
let dataNew = DataLoader().beatLoops
var songs: [String] = []
var audioPlayer: AVAudioPlayer!
//MARK: - SONG METHODS
func playLoop(songName: String) {
let url = Bundle.main.url(forResource: songName, withExtension: ".mp3") // you should check it for errors
audioPlayer = try! AVAudioPlayer(contentsOf: url!) // of course you should catch the error and deal with it...
audioPlayer.play()
}
func gettingSongName() {
let folderURL = URL(fileURLWithPath: Bundle.main.resourcePath!)
do {
let songPath = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for song in songPath {
var mySong = song.absoluteString
if mySong.contains(".mp3") {
let findString = mySong.components(separatedBy: "/")
mySong = findString[findString.count - 1]
mySong = mySong.replacingOccurrences(of: "%20", with: " ")
mySong = mySong.replacingOccurrences(of: ".mp3", with: "")
songs.append(mySong)
}
}
} catch {
}
}
@IBOutlet var backButtonView: UIView!
@IBOutlet weak var baxkButtonView: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
beatTableView.delegate = self
beatTableView.dataSource = self
// parseJSON()
self.view.backgroundColor = SettingsService.sharedService.backgroundColor
coverImage.layer.cornerRadius = 20
coverImage.layer.shadowRadius = 7
coverImage.layer.shadowOpacity = 0.8
coverImage.layer.shadowOffset = CGSize(width: 3, height: 3)
coverImage.layer.shadowColor = UIColor.black.cgColor
coverImage.clipsToBounds = true
gettingSongName()
self.navigationController?.setNavigationBarHidden(true, animated: false)
let backButton = UIBarButtonItem(customView: self.baxkButtonView)
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
visualEffectView.frame = (self.navigationController?.navigationBar.bounds.insetBy(dx: 0, dy: -30).offsetBy(dx: 0, dy: -20))!
self.navigationController?.navigationBar.isTranslucent = true
// self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
// self.navigationController?.navigationBar.addSubview(visualEffectView)
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
// let backButtonItem = UIBarButtonItem(customView: self.backButton)
self.navigationController?.delegate = self
// self.navigationItem.leftBarButtonItem = backButtonItem
// self.navigationItem.leftBarButtonItem?.action = #selector(self.back(sender:))
self.navigationItem.leftBarButtonItem = backButton
}
@objc func back(sender: UIBarButtonItem) {
self.navigationController?.popViewController(animated: true)
print("done")
}
}
//MARK: TABLEVIEW DATASOURCE, DELEGATE METHODS
extension BeatPackViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataNew.count
// return dataNew.beatLoops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomLoopsCell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
// gettingSongName()
cell.loopNameLabel.text = dataNew[indexPath.row].name
// cell.loopNameLabel.text = dataNew.beatLoops[indexPath.row].name
// cell.producerLabel.text = dataNew.beatLoops[indexPath.row].producer
// cell.instrumentLabel.text = dataNew.beatLoops[indexPath.row].instrument
cell.delegate = self
cell.selectionStyle = .none
cell.tag = indexPath.row
// cell.playButtonOutlet.tag = indexPath.row
if let playingCell = currentPlayingIndex, playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
return cell
}
//MARK: - Button Check
func btnUseTap(cell: CustomLoopsCell) {
let indexPath = self.beatTableView.indexPath(for: cell)
if currentPlayingIndex == cell.tag {
audioPlayer.pause()
currentPlayingIndex = nil
} else { //IF PAUSE BUTTON
playLoop(songName: songs[cell.tag])
currentPlayingIndex = cell.tag
}
beatTableView.reloadData()
// playSong(index: indexPath!.row)
print("Done")
}
2
Answers
In the
viewDidLoad()
function of your view controller, make sure you run your dataLoader to fetch the data.After the data is loaded in the data loader, you need to send a message to the view controller saying data is ready to display.
You can do this through:
@Published
attribute you haveThere are lots of ways to do this part, and I can explain in more detail if you pick one.
Once you have the data, run
reloadData()
on the table view.You are continuously editing and changing the data, the latest version messed everything up.
This is a tested working version, please read and copy/paste the data carefully.
This is the JSON (a bit shortened)
The corresponding structs are
The
@Publisher
attribute makes no sense without Combine, replace the class with the following, the parser returns theBeatPackData
object ornil
in case of an errorThis is the relevant code in the view controller