从 'table view' 和 'filtered search bar' 中删除行不起作用
Deleting rows from 'table view' & 'filtered search bar' is not working
我正在 Swift 上制作一个音乐播放应用程序,它可以将音乐从计算机的网络浏览器 (GCD WebUploader) 上传到应用程序的文档目录中。现在我可以在 table 视图中显示所有歌曲,并且搜索栏也能正常工作。
但是我在执行删除功能时遇到了问题。好的,2 个问题。
1) 当我滑动以从 table 视图中删除一行时,我可以看到该行消失了。但是当我强制这个应用程序并重新启动它时,删除的应用程序仍然存在。 -> 我需要对所有功能或某些数据库使用异步吗?我碰壁了,需要帮助。
2) 当我滑动以从搜索栏中删除一行时,该行已从搜索栏中删除,但是当切换回来时,该歌曲仍在 table 视图中。
/// 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
}
}
/// Table 包含搜索栏的视图控制器
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{ [=13=].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: {[=13=].commonKey == .commonKeyTitle}), let value = songTitle.value as? String {
songName = value
} else {
songName = "No song name"
}
if let artist = metaData.first(where: {[=13=].commonKey == .commonKeyArtist}), let value = artist.value as? String {
artistName = value
} else {
artistName = "No artist name"
}
if let album = metaData.first(where: {[=13=].commonKey == .commonKeyAlbumName}), let value = album.value as? String {
albumName = value
} else {
albumName = "No album name"
}
if let albumImage = metaData.first(where: {[=13=].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
到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
}
}
现在在如下搜索时从 tableData
中删除 songData
对象,
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{}
,以后想改进和学习。
/ / /代码有点乱,注释很多,我只是想把事情说清楚。简而言之,在搜索栏中删除 song/row 时,您需要从本地文件、数据源、过滤后的数据源以及用于播放的 URL 数组 directoryContents 中删除歌曲我的应用程序中的歌曲。
注意!如果两首歌曲同名,那么我的解决方案将失败。
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: { [=10=].songName == selected_song.songName && [=10=].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)")
}
}
}
我正在 Swift 上制作一个音乐播放应用程序,它可以将音乐从计算机的网络浏览器 (GCD WebUploader) 上传到应用程序的文档目录中。现在我可以在 table 视图中显示所有歌曲,并且搜索栏也能正常工作。
但是我在执行删除功能时遇到了问题。好的,2 个问题。
1) 当我滑动以从 table 视图中删除一行时,我可以看到该行消失了。但是当我强制这个应用程序并重新启动它时,删除的应用程序仍然存在。 -> 我需要对所有功能或某些数据库使用异步吗?我碰壁了,需要帮助。
2) 当我滑动以从搜索栏中删除一行时,该行已从搜索栏中删除,但是当切换回来时,该歌曲仍在 table 视图中。
/// 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
}
}
/// Table 包含搜索栏的视图控制器
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{ [=13=].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: {[=13=].commonKey == .commonKeyTitle}), let value = songTitle.value as? String {
songName = value
} else {
songName = "No song name"
}
if let artist = metaData.first(where: {[=13=].commonKey == .commonKeyArtist}), let value = artist.value as? String {
artistName = value
} else {
artistName = "No artist name"
}
if let album = metaData.first(where: {[=13=].commonKey == .commonKeyAlbumName}), let value = album.value as? String {
albumName = value
} else {
albumName = "No album name"
}
if let albumImage = metaData.first(where: {[=13=].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
到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
}
}
现在在如下搜索时从 tableData
中删除 songData
对象,
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{}
,以后想改进和学习。
/ / /代码有点乱,注释很多,我只是想把事情说清楚。简而言之,在搜索栏中删除 song/row 时,您需要从本地文件、数据源、过滤后的数据源以及用于播放的 URL 数组 directoryContents 中删除歌曲我的应用程序中的歌曲。
注意!如果两首歌曲同名,那么我的解决方案将失败。
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: { [=10=].songName == selected_song.songName && [=10=].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)")
}
}
}