Swift 从';表视图'&';过滤搜索栏';它不起作用了

Swift 从';表视图'&';过滤搜索栏';它不起作用了,swift,tableview,searchbar,swift5,Swift,Tableview,Searchbar,Swift5,我正在Swift上制作一个音乐播放应用程序,它可以将音乐从计算机的web浏览器(GCD WebUploader)上传到应用程序的文档目录中。现在我可以在表视图中显示所有歌曲,搜索栏也正常工作 但我在实现delete func时遇到问题。好的,两个问题 1) 当我从表视图中滑动删除一行时,我可以看到该行消失了。但当我强制此应用程序并重新启动时,删除的应用程序仍然存在。->我需要对所有FUNC或某些数据库使用异步吗?我撞墙了,需要帮助 2) 当我从搜索栏中滑动删除一行时,该行将从搜索栏中删除,但当切

我正在Swift上制作一个音乐播放应用程序,它可以将音乐从计算机的web浏览器(GCD WebUploader)上传到应用程序的文档目录中。现在我可以在表视图中显示所有歌曲,搜索栏也正常工作

但我在实现delete func时遇到问题。好的,两个问题

1) 当我从表视图中滑动删除一行时,我可以看到该行消失了。但当我强制此应用程序并重新启动时,删除的应用程序仍然存在。->我需要对所有FUNC或某些数据库使用异步吗?我撞墙了,需要帮助

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()
        }
    }
}


SongData
equalable
一致,如下所示

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
    }
}
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)")
        }
    }
}
现在从
tableData
中删除
songData
对象,同时进行如下搜索:

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

那么,在这里更新我的代码。作为Kamran的方法,使用tableData.firstIndex(of:song)将搜索栏中选定的歌曲与tableData中的歌曲进行比较。但是我没有在我的SongData文件中使用Equatable。实际上,我现在还不完全理解这个语法
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)")
            }
        }
    }

您只从过滤后的数据数组中删除歌曲,而不是从主数组和应用程序目录中删除歌曲,因此不会删除歌曲。1)您需要从用于创建数据源的本地文件中删除该歌曲,即,
tableData
。2) 从
filteredTableData
中删除不相关但从不将类中使用非可选值初始化的属性声明为可选的属性时,也从源数组
tableData
中删除项。删除
SongData
中的所有问号。代码将被编译。@Kamran,同意你的说法!我将代码更改为在FileManager级别添加文件删除,现在删除操作在表视图上进行。但对于搜索栏过滤器视图,我怎么知道应该从应用程序的documentDirectory中删除哪一个呢?我的意思是,过滤视图的索引将与本地文件中的索引不同。@vadian,很高兴再次见到你:)我将删除那些问号,相信我。感谢解决方案,它让我知道了这个
tableData。firstIndex(of:song)
方法,因为我是Swift和自学的新手,找到要使用的API/库以及如何实现它们确实花费了我大量的时间,但感谢Google和stackoverflow,给了我找到答案的方法。Ps.
tableData.firstIndex(of:song)
需要相等的类型,但我没有更改我的歌曲数据,我找到了另一个解决方案
tableData.firstIndex(其中:{$0.songName==selected\u song.songName})
。好的,我将更新我的代码如下(在iPhone上测试后),谢谢你的帮助。