Удаление строк из «табличного представления» и «отфильтрованной панели поиска» не работает

Я делаю приложение для воспроизведения музыки на Swift, которое может загружать музыку из веб-браузера компьютера (GCD WebUploader) в каталог документов приложения. Теперь я мог показать все песни в виде таблицы, а также панель поиска работает правильно.

Но у меня возникают проблемы при реализации функции удаления. Хорошо, 2 вопроса.

1) когда я прокручиваю, чтобы удалить строку из представления таблицы, я вижу, что эта строка исчезла. Но когда я принудительно запускаю это приложение и перезапускаю его, удаленное остается. -> Нужно ли мне использовать асинхронность для всех функций или некоторой базы данных? Я ударился о стену и нуждаюсь в помощи.

2) когда я прокручиваю, чтобы удалить строку из панели поиска, она удаляется из панели поиска, но когда я переключаюсь обратно, эта песня все еще находится в представлении таблицы.

введите здесь описание изображения

/// SongData.swift используется для хранения метаданных песни.

import Foundation
import UIKit

class SongData {
    var songName: String?
    var artistName: String?
    var albumName: String?
    var albumArtwork: UIImage?
    var url: URL?

    init(songName: String, artistName: String, albumName: String, albumArtwork: UIImage, url: URL) {
        self.songName = songName
        self.artistName = artistName
        self.albumName = albumName
        self.albumArtwork = albumArtwork
        self.url = url
    }

}

/// Контроллер табличного представления, включая панель поиска

import UIKit
import AVKit
import AVFoundation


class SongsTableViewController: UITableViewController, UISearchResultsUpdating{

    var directoryContents = [URL]()
    var songName: String?
    var artistName: String?
    var albumName: String?
    var albumArtwork: UIImage?

    var audioPlayer: AVAudioPlayer!
    var resultSearchController = UISearchController()

    // create type SongData array to store song's metaData.
    var tableData = [SongData]()
    var filteredTableData = [SongData]()

    override func viewDidLoad() {
        super.viewDidLoad()

        do {

            // Get the document directory url
            let documentsUrl =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

            // Get the directory contents urls (including subfolders urls)
            directoryContents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil)

            // if you want to filter the directory contents you can do like this:
            let mp3Files = directoryContents.filter{ $0.pathExtension == "mp3" }

            // get music metadata (artist, album...)
            for url in mp3Files {
                let asset = AVAsset(url: url)
                let metaData = asset.metadata

                if let songTitle = metaData.first(where: {$0.commonKey == .commonKeyTitle}), let value = songTitle.value as? String {
                    songName = value
                } else {
                    songName = "No song name"
                }

                if let artist = metaData.first(where: {$0.commonKey == .commonKeyArtist}), let value = artist.value as? String {
                    artistName = value
                } else {
                    artistName = "No artist name"
                }

                if let album = metaData.first(where: {$0.commonKey == .commonKeyAlbumName}), let value = album.value as? String {
                    albumName = value
                } else {
                    albumName = "No album name"
                }

                if let albumImage = metaData.first(where: {$0.commonKey == .commonKeyArtwork}), let value = albumImage.value as? Data {
                    albumArtwork = UIImage(data: value)
                } else {
                    albumArtwork = UIImage(named: "Apple")
                    print("artWork is not found!")
                }

                tableData.append(SongData(songName: songName!, artistName: artistName!, albumName: albumName!, albumArtwork: albumArtwork!, url: url))
            }

        } catch {
            print(error)
        }

        // add search bar
        resultSearchController = ({
            let controller = UISearchController(searchResultsController: nil)
            controller.searchResultsUpdater = self
            controller.dimsBackgroundDuringPresentation = false
            controller.searchBar.sizeToFit()

            self.tableView.tableHeaderView = controller.searchBar

            return controller
            })()

        // reload the table
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if resultSearchController.isActive {
            return filteredTableData.count
        } else {
            return tableData.count
        }

    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)

        if resultSearchController.isActive {
            cell.textLabel?.text = filteredTableData[indexPath.row].songName
            cell.detailTextLabel?.text = filteredTableData[indexPath.row].artistName
            cell.imageView?.image = filteredTableData[indexPath.row].albumArtwork
            return cell
        } else {
            cell.textLabel?.text = tableData[indexPath.row].songName
            cell.detailTextLabel?.text = tableData[indexPath.row].artistName
            cell.imageView?.image = tableData[indexPath.row].albumArtwork
            return cell
        }
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        if resultSearchController.isActive {
            do {
                // this part is not started yet
                try audioPlayer = AVAudioPlayer(contentsOf: filteredTableData[indexPath.row].url!)
                audioPlayer.prepareToPlay()
                audioPlayer.play()
            } catch {
                print("could not load file")
            }
        } else {
            do {
                try audioPlayer = AVAudioPlayer(contentsOf: directoryContents[indexPath.row])
                audioPlayer.prepareToPlay()
                audioPlayer.play()
            } catch {
                print("could not load file")
            }
        }
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete {
            if resultSearchController.isActive {
                filteredTableData.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
            } else {
                tableData.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
                // remove data from local source
                try! FileManager.default.removeItem(at: directoryContents[indexPath.row])
                print("delete song at index \(indexPath.row)")
            }
        }
    }

    func updateSearchResults(for searchController: UISearchController) {
        filteredTableData.removeAll(keepingCapacity: false)

        let searchText = searchController.searchBar.text!
        for item in tableData {
            let str = item.songName
            if str!.lowercased().contains(searchText.lowercased()) {
                filteredTableData.append(item)
            }
        }

        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
}


person ChuckZHB    schedule 29.08.2019    source источник
comment
вы удаляете только из отфильтрованного массива данных, а не из основного массива и из каталога приложения, поэтому песни не удаляются.   -  person nothingwhatsoever    schedule 29.08.2019
comment
1) Вам нужно удалить эту песню из локального файла, который вы используете для создания источника данных, то есть tableData. 2) Удалить элемент из исходного массива tableData, а также удалить из filteredTableData   -  person Kamran    schedule 29.08.2019
comment
Не связаны, но никогда не объявляют свойства в классе как необязательные, которые инициализируются необязательными значениями. Удалите все вопросительные знаки в SongData. Код будет скомпилирован.   -  person vadian    schedule 29.08.2019
comment
@Kamran, согласен с тобой! Я меняю свой код, чтобы добавить удаление файлов на уровне FileManager, теперь удаление работает в представлении таблицы. Но для представления фильтра панели поиска, как я могу узнать, какой из них должен быть удален из documentDirectory приложения? Я имею в виду, что индекс отфильтрованного представления будет отличаться от индекса в локальном файле.   -  person ChuckZHB    schedule 29.08.2019
comment
@vadian, приятно снова тебя видеть :) Я уберу эти вопросительные знаки, поверь мне.   -  person ChuckZHB    schedule 29.08.2019


Ответы (2)


Соответствуйте SongData Equatable, как показано ниже,

class SongData: Equatable {

    static func == (lhs: SongData, rhs: SongData) -> Bool {
        return lhs.songName == rhs.songName &&
                lhs.artistName == rhs.artistName &&
                lhs.albumName == rhs.albumName &&
                lhs.albumArtwork == rhs.albumArtwork
    }

    var songName: String?
    var artistName: String?
    var albumName: String?
    var albumArtwork: UIImage?
    var url: URL?

    init(songName: String, artistName: String, albumName: String, albumArtwork: UIImage, url: URL) {
        self.songName = songName
        self.artistName = artistName
        self.albumName = albumName
        self.albumArtwork = albumArtwork
        self.url = url
    }
}

Теперь удалите объект songData из tableData во время поиска, как показано ниже,

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == UITableViewCell.EditingStyle.delete {
        if resultSearchController.isActive {
            let song = filteredTableData[indexPath.row]
            if let index = tableData.firstIndex(of: song) {
                try! FileManager.default.removeItem(at: directoryContents[index])
                tableData.remove(at: index)
            }

            filteredTableData.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
        } else {
            tableData.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
            // remove data from local source
            try! FileManager.default.removeItem(at: directoryContents[indexPath.row])
            print("delete song at index \(indexPath.row)")
        }
    }
}
person Kamran    schedule 29.08.2019
comment
спасибо за решение, оно дало мне знать этот метод tableData.firstIndex(of: song), так как я новичок в Swift и учусь в одиночку, чтобы найти, какой API/библиотеку использовать и как их реализовать, мне действительно потребовалось много времени, но спасибо за Google и stackoverflow , давая мне способ найти ответ. Пс. tableData.firstIndex(of: song) требуются типы Equatable, но я не меняю данные песни, я нашел другое решение tableData.firstIndex(where: { $0.songName == selected_song.songName }). Что ж, я обновлю свой код, как показано ниже (после теста на iPhone), спасибо за вашу помощь. - person ChuckZHB; 29.08.2019

Хорошо, обновите мой код здесь. В качестве метода Камрана использовать tableData.firstIndex(of: song) для сравнения выбранной песни в строке поиска с песней в tableData. Но я не использую Equatable в моем файле SongData. На самом деле, я не совсем понимаю эту грамматику сейчас static func == (lhs: SongData, rhs: SongData) -> Bool{}, хотел бы улучшить и выучить ее позже.

// / Код довольно запутанный с кучей комментариев, я просто хочу прояснить ситуацию. Короче говоря, при удалении песни/строки в строке поиска вам нужно удалить песню из вашего локального файла, источника данных, отфильтрованного источника данных, а также directoryContents, который представляет собой массив URL-адресов, используемый для воспроизведения песни в моем приложении.

Примечание! Если две песни имеют одинаковое имя, то мое решение не удастся.

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete {
            if resultSearchController.isActive {
                // get selected song from search bar
                let selected_song = filteredTableData[indexPath.row]
                // get song index in tableData where condition is
                // tableData's item.songName == selected song's songName
                if let index = tableData.firstIndex(where: { $0.songName == selected_song.songName && $0.albumName == selected_song.albumName}) {
                    // remove data from local source
                    try! FileManager.default.removeItem(at: directoryContents[index])
                    // remove url item from dir contents [URL], which is using for audio playing
                    directoryContents.remove(at: index)
                    // remove song from tableData
                    tableData.remove(at: index)
                }

                // remove song from filteredTableData
                filteredTableData.remove(at: indexPath.row)
                // delete row with selected song from tableView in search bar
                tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
            } else {
                tableData.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
                // remove data from local source
                try! FileManager.default.removeItem(at: directoryContents[indexPath.row])
                // remove url from dir contents [URL], which is using for audio playing
                directoryContents.remove(at: indexPath.row)
                print("delete song at index \(indexPath.row)")
            }
        }
    }
person ChuckZHB    schedule 29.08.2019