Home > OS >  How to load data from local JSON to the labels in table view using swift
How to load data from local JSON to the labels in table view using swift

Time:09-17

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: " ", 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")
    }

CodePudding user response:

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)

{
    "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"
            }
        ],
        "loops": [
            {
                "name": "Away we go",
                "producer": "Tubular Kingz",
                "count": "28",
                "genre": "Lo-fi Hip Hop"
            },
            {
                "name": "Test",
                "producer": "Testing",
                "count": "25",
                "genre": "Lo-fi"
            }
        ]
    }
}

The corresponding structs are

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
}

The @Publisher attribute makes no sense without Combine, replace the class with the following, the parser returns the BeatPackData object or nil in case of an error

public class DataLoader {
    
    func parseJSON() -> BeatPackData? {
        guard let url = Bundle.main.url(forResource: "data", withExtension: "json") else {
            print("\n-------> bundle path error")
            return nil
        }
        
        do {
            let jsonData = try Data(contentsOf: url)
            let response = try JSONDecoder().decode(BeatData.self, from: jsonData)
            print("\n-------> response: \(response)")
            return response.data
        }
        catch {
            print("\n====> error: \(error)" )
            return nil
        }
        
    }
}

This is the relevant code in the view controller

class BeatPackViewController: UIViewController {
    
    @IBOutlet weak var beatTableView: UITableView!
    
    var loops = [Loop]()
    var beatLoops = [BeatLoop]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let loader = DataLoader()
        guard let data = loader.parseJSON() else { return }
        loops = data.loops
        beatLoops = data.beatloops
        beatTableView.reloadData()
    }
}

extension BeatPackViewController : UITableViewDataSource, UITableViewDelegate {
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return beatLoops.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: CustomLoopsCell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
        let beatLoop = beatLoops[indexPath.row]
        
        cell.loopNameLabel.text = beatLoop.name
        cell.producerLabel.text = beatLoop.producer
        cell.instrumentLabel.text = beatLoop.instrument
        
        return cell
    }
}

CodePudding user response:

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:

  • a protocol/delegate
  • using a closure as a completion handler
  • setting up an observer for the @Published attribute you have
  • (if you're on the iOS 15 beta) using async/await There 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.

  • Related