Search code examples
swiftxcodeuitableviewdelete-row

How do I delete a row in a UITableView where the var is an integer?


I'm creating an audio voice recorder and after each upload, the name appends an int to the recording (e.g. 1, 2, 3, etc.) in a table view (note: regular view controller with a UITableView vs. Table View Controller).

I'm having trouble deleting each row, and I'm not sure if it is because 'numberOfRecords.remove(at: indexPath.row)' only accepts strings.

I get the error: "Value of type 'Int' has no member 'remove.'"

class ViewController2: UIViewController, RecordButtonDelegate, AVAudioRecorderDelegate, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var myTableView: UITableView!

var numberOfRecords : Int = 0

// Setting up Table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return numberOfRecords
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = String(indexPath.row + 1)

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let path = getDirectory().appendingPathComponent("\(indexPath.row + 1).m4a")

    do {
        audioPlayer = try AVAudioPlayer(contentsOf: path)
        audioPlayer.play()
    }

    catch {

    }
}

// Delete rows
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == UITableViewCellEditingStyle.delete{
        numberOfRecords.remove(at: indexPath.row)

        tableView.beginUpdates()
        tableView.deleteRows(at: [indexPath], with: .automatic)
        tableView.endUpdates()

    }
}


// Audio Player
var audioPlayer : AVAudioPlayer!
var recordingSession : AVAudioSession!
var audioRecorder : AVAudioRecorder!
var recordButton: RecordButton?

@IBOutlet weak var buttonLabel2: RecordButton!

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    buttonLabel2.delegate = self

}

func tapButton(isRecording: Bool) {

    // Check if we have an active recorder
    if audioRecorder == nil {
        numberOfRecords += 1
        let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")

        let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                        AVSampleRateKey: 12000,
                        AVNumberOfChannelsKey: 1,
                        AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]

        // Start audio recording
        do {
            audioRecorder = try AVAudioRecorder(url: filename, settings: settings)
            audioRecorder.delegate = self
            audioRecorder.record()

        }

        catch {
            displayAlert(title: "Oops!", message: "Recording failed")
        }

        // Play speaker instead of earpiece
        let audioSession = AVAudioSession.sharedInstance()

        do {
            try audioSession.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)

        } catch let error as NSError {
            print("Audio Session error: \(error.localizedDescription)")
        }


    }
    else {
        // Stop audio recording
        audioRecorder.stop()
        audioRecorder = nil

        UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
        myTableView.reloadData()

    }

}


override func viewDidLoad() {
    super.viewDidLoad()

    // Setting up Recording session
    recordingSession = AVAudioSession.sharedInstance()

    if let number : Int = UserDefaults.standard.object(forKey: "myNumber") as? Int {
        numberOfRecords = number
    }

    AVAudioSession.sharedInstance().requestRecordPermission { (hasPermission) in
        if hasPermission {
            print ("Accepted")
        }
    }

Solution

  • The problem that you're experiencing is due you calling remove(at:) on an Int. An Int has no function called remove(at:).

    You're declaring var numberOfRecords: Int to track your indices, then in

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
    

    you're calling

    // this is the line that's causing your problem
    numberOfRecords.remove(at: indexPath.row)
    

    If you want to continue using an int to track your cells you should subtract from numberOfRecords

    numberOfRecords-=1
    

    Or you could track your records using an array something like this:

    // declare an array of Strings to hold your filenames
    var records: [String] = []
    

    then where you're saving your files add the new filename to your array for the tableview

    // Stop audio recording
    audioRecorder.stop()
    audioRecorder = nil
    
    // add your filename to your array of records for the tableview
    records.append(filename)
    
    // update your total number of records if desired
    UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
    myTableView.reloadData()
    

    Then your delegate functions might look something like this

    // Setting up Table View
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // return the total count of filenames in your array
        return records.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    
        // set the filename in your text label
        cell.textLabel?.text = records[indexPath.row]
    
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
        // get the filename for this row
        let filename = records[indexPath.row]
    
        // use your filename in your path
        let path = getDirectory().appendingPathComponent("\(filename)")
    
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: path)
            audioPlayer.play()
        }
    
        catch {
    
        }
    }
    

    and you could update your remove(at:) call to run on the records array instead

    records.remove(at: indexPath.row)
    

    EDIT:

    When you add or remove filenames from your records array update user defaults with your updated records:

    // save array to user defaults when you create a new record or when you delete a record
    UserDefaults.standard.setValue(records, forKey: "storedRecords")
    

    To retrieve your saved array of filenames pull them from user defaults and update your records array with the stored names.

    Replace these lines in your viewDidLoad function:

    if let number : Int = UserDefaults.standard.object(forKey: "myNumber") as? Int {
        numberOfRecords = number
    }
    

    with this:

    // load stored records from user defaults and verify it's what you expect to receive
    if let stored = UserDefaults.standard.value(forKey: "storedRecords") as? [String] {
        // update your records array with the stored values
        records = stored
    }