如何以编程方式 select UICollection 查看单元格并更新指针数组?
How can I programmatically select UICollection view cells and update a pointer array?
我将 indexPath 与指针数组与我的数据模型进行匹配,以查看正在播放的曲目,以便我可以使用 didSelectItemAt 委托方法控制音频播放。即,当点击一个单元格时,它会根据 selected 单元格的 indexPath 检查正在播放的歌曲。 (我正在根据这些参数在 didSelectItemAt 委托中播放和暂停音频)。我通过在给定 indexPath 的轨道的指针数组中切换一个布尔值来做到这一点。
这非常适合在您手动 select 单元格(点击或单击)时播放和暂停音频。
我想以编程方式 select 其他单元格,但是当我这样做时,似乎布尔值设置不正确。
这里有一些代码可以帮助解释:
let workData = TypeData.createWorkMusicArray()
var musicDataArray: [TypeData] = []
var keys = [TypeData]()
var pointerArray: [TypeData : Bool]!
var currentTrack: Int!
override func viewDidLoad() {
super.viewDidLoad()
musicDataArray = workData
keys = [workData[0], workData[1], workData[2]]
pointerArray = [workData[0] : false, workData[1] : false, workData[2] : false]
musicCollectionView.allowsMultipleSelection = false
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentTrack = indexPath.item
if pointerArray[keys[indexPath.item]] == false {
pointerArray[keys[indexPath.item]] = true
print("Playing", keys[indexPath.item])
playAudio()
return
}
pointerArray[keys[indexPath.item]] = false
print("Stop", keys[indexPath.item])
pause()
}
另请注意,我没有将 didDeselectItemAt 委托方法用于我的应用程序中的任何播放逻辑,因为我只有一个 AVPlayer,并且当单元格为 selected(此处的 deselect 方法只是将布尔值设置为 false,因此我可以更改 UI)
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
pointerArray[keys[indexPath.item]] = false
print("Stop", keys[indexPath.item])
}
这是我用来尝试以编程方式 select 单元格的函数:
func previousTrack() {
func getCurrentTrack() {
if currentTrack - 1 < 0 {
currentTrack = (musicDataArray.count - 1) < 0 ? 0 : (musicDataArray.count - 1)
} else {
currentTrack -= 1
}
}
getCurrentTrack()
//I've tried setting the boolean here to false but it still does not work
pointerArray[keys[currentTrack]] = false
self.musicCollectionView.selectItem(at: IndexPath(item: currentTrack, section: 0), animated: true, scrollPosition: .top)
self.musicCollectionView.delegate?.collectionView!(self.musicCollectionView, didSelectItemAt: IndexPath(item: currentTrack, section: 0))
}
@IBAction func rewindBtnTapped(_ sender: Any) {
previousTrack()
}
当调用 rewindBtnTapped 时,它将 select 前一个单元格,但是当我决定 select/tap/click 一个单元格时,行为不一致,即启用播放和暂停的布尔值已经搞混了。
非常感谢您的宝贵时间 - 谢谢
我之前发布的代码只是向您展示了一些关于使用变量告诉您歌曲是否正在播放的逻辑。但这不是一个好的生产代码,它主要用于说明目的(维护字典和指针数组并不容易)。所以,我将尝试以不同的方式指导您,以便您的应用程序在以后变得更易于阅读,并且您可以尽可能多地从控制器中分离播放功能 -> 原因:想象一下,稍后您想要重用相同的单元格来填充 collection 视图,例如显示与专辑相对应的歌曲。然后您将不得不重构所有代码以使新的 collection 视图也能正常工作。但是,任何面向 object 的编程的要点是您实际上可以构建 "Objects" 可以根据需要重复使用的内容。
所以,首先想一想您的 "song" object 究竟是什么。很可能不仅是一个文件的路径,只要用户从您的 collection 视图中点击一个单元格,该文件就会被播放。我想一首歌也会有图像、长度、名称、专辑等。如果您继续为歌曲需要显示的每个变量创建数组,您最终可能会得到无数需要单独维护的数组.这并不容易,而且肯定更容易失败。
第一件事是创建一个 class 或一个实际包含歌曲所有数据的结构。例如(使用结构 - 如果需要,可以使用 class):
struct Song {
var songName : String
var isPlaying : Bool = false
init(songName : String) {
self.songName = songName
}
}
我只为歌曲 object 使用了两个变量,但您可以根据需要添加任意多个变量。只需注意在创建"Song" object 实例时可以初始化哪个变量即可。示例中只能初始化歌曲名,播放的Bool默认初始化为false,是判断歌曲是否播放的变量。
然后您可以设置您的 Cell,在这种情况下,它将根据您的 "Song" 负责设置所有视图。为此,您可以为单元格创建一个自定义 class,在这种情况下,我只是将其命名为 Cell,您可以随意调用它。
class Cell : UICollectionViewCell {
var song : Song?
weak var cellDelegate : CellDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .red
}
func play() {
if !(song?.isPlaying)! {
song?.isPlaying = true
print("Playing", (song?.songName)!)
return
}
song?.isPlaying = false
print("Stop", (song?.songName)!)
}
}
请注意,单元格具有 play() 函数和一个名为 song var song : Song?
的变量。原因是我要让每个单元格决定何时播放或暂停。这会将单元格与您将在 collection 视图中使用的播放功能分离。
现在您有了 "Song" object,您可以轻松地在视图控制器或任何其他 class 中创建歌曲数组。例如:
var songArray : [Song] = [Song.init(songName: "Song One"), Song.init(songName: "Song Two"), Song.init(songName: "Song Three")]
最后,您可以使用歌曲数组中的每首歌曲初始化 collection 视图的每个单元格。为此,您将使用 cellForItemAt
函数:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.song = songArray[indexPath.item]
cell.cellDelegate = self
return cell
}
现在,您会看到单元格还有一个名为 var cellDelegate : CellDelegate
的弱变量,这是您将用来控制单元格播放和暂停功能的协议。您可以稍后进一步阅读有关委托的内容,以及它们如何帮助您尽可能地将您的单元与控制器分离。此外,另一个 collection 视图控制器可能符合此委托,您将拥有对单元格功能的相同访问权限,而无需重写所有代码。
协议可以在您的手机外设置 class:
protocol CellDelegate : class {
func didSelectCell (for cell: Cell)
}
最后,使视图控制器符合 CellDelegate:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, CellDelegate
现在播放、暂停、上一个、下一个等的代码变得更简单、更清晰
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentIndexPathItem = indexPath.item
let cellToPlay = collectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as! Cell
didSelectCell(for: cellToPlay)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cellToStop = collectionView.cellForItem(at: indexPath) as! Cell
if (cellToStop.song?.isPlaying)! { didSelectCell(for: cellToStop) }
}
@objc func previous () {
let playingCell = collectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as! Cell
if (playingCell.song?.isPlaying)! { didSelectCell(for: playingCell) }
if currentIndexPathItem - 1 < 0 { return }
else { currentIndexPathItem = currentIndexPathItem - 1 }
let cellToPlay = collectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as! Cell
didSelectCell(for: cellToPlay)
}
func didSelectCell(for cell: Cell) {
cell.play()
}
请注意 didSelectCell
函数,这是您需要添加的所有内容以符合委托并播放或暂停您的歌曲。就是这样。代码更简洁、更简单。
我将 indexPath 与指针数组与我的数据模型进行匹配,以查看正在播放的曲目,以便我可以使用 didSelectItemAt 委托方法控制音频播放。即,当点击一个单元格时,它会根据 selected 单元格的 indexPath 检查正在播放的歌曲。 (我正在根据这些参数在 didSelectItemAt 委托中播放和暂停音频)。我通过在给定 indexPath 的轨道的指针数组中切换一个布尔值来做到这一点。
这非常适合在您手动 select 单元格(点击或单击)时播放和暂停音频。
我想以编程方式 select 其他单元格,但是当我这样做时,似乎布尔值设置不正确。
这里有一些代码可以帮助解释:
let workData = TypeData.createWorkMusicArray()
var musicDataArray: [TypeData] = []
var keys = [TypeData]()
var pointerArray: [TypeData : Bool]!
var currentTrack: Int!
override func viewDidLoad() {
super.viewDidLoad()
musicDataArray = workData
keys = [workData[0], workData[1], workData[2]]
pointerArray = [workData[0] : false, workData[1] : false, workData[2] : false]
musicCollectionView.allowsMultipleSelection = false
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentTrack = indexPath.item
if pointerArray[keys[indexPath.item]] == false {
pointerArray[keys[indexPath.item]] = true
print("Playing", keys[indexPath.item])
playAudio()
return
}
pointerArray[keys[indexPath.item]] = false
print("Stop", keys[indexPath.item])
pause()
}
另请注意,我没有将 didDeselectItemAt 委托方法用于我的应用程序中的任何播放逻辑,因为我只有一个 AVPlayer,并且当单元格为 selected(此处的 deselect 方法只是将布尔值设置为 false,因此我可以更改 UI)
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
pointerArray[keys[indexPath.item]] = false
print("Stop", keys[indexPath.item])
}
这是我用来尝试以编程方式 select 单元格的函数:
func previousTrack() {
func getCurrentTrack() {
if currentTrack - 1 < 0 {
currentTrack = (musicDataArray.count - 1) < 0 ? 0 : (musicDataArray.count - 1)
} else {
currentTrack -= 1
}
}
getCurrentTrack()
//I've tried setting the boolean here to false but it still does not work
pointerArray[keys[currentTrack]] = false
self.musicCollectionView.selectItem(at: IndexPath(item: currentTrack, section: 0), animated: true, scrollPosition: .top)
self.musicCollectionView.delegate?.collectionView!(self.musicCollectionView, didSelectItemAt: IndexPath(item: currentTrack, section: 0))
}
@IBAction func rewindBtnTapped(_ sender: Any) {
previousTrack()
}
当调用 rewindBtnTapped 时,它将 select 前一个单元格,但是当我决定 select/tap/click 一个单元格时,行为不一致,即启用播放和暂停的布尔值已经搞混了。
非常感谢您的宝贵时间 - 谢谢
我之前发布的代码只是向您展示了一些关于使用变量告诉您歌曲是否正在播放的逻辑。但这不是一个好的生产代码,它主要用于说明目的(维护字典和指针数组并不容易)。所以,我将尝试以不同的方式指导您,以便您的应用程序在以后变得更易于阅读,并且您可以尽可能多地从控制器中分离播放功能 -> 原因:想象一下,稍后您想要重用相同的单元格来填充 collection 视图,例如显示与专辑相对应的歌曲。然后您将不得不重构所有代码以使新的 collection 视图也能正常工作。但是,任何面向 object 的编程的要点是您实际上可以构建 "Objects" 可以根据需要重复使用的内容。
所以,首先想一想您的 "song" object 究竟是什么。很可能不仅是一个文件的路径,只要用户从您的 collection 视图中点击一个单元格,该文件就会被播放。我想一首歌也会有图像、长度、名称、专辑等。如果您继续为歌曲需要显示的每个变量创建数组,您最终可能会得到无数需要单独维护的数组.这并不容易,而且肯定更容易失败。
第一件事是创建一个 class 或一个实际包含歌曲所有数据的结构。例如(使用结构 - 如果需要,可以使用 class):
struct Song {
var songName : String
var isPlaying : Bool = false
init(songName : String) {
self.songName = songName
}
}
我只为歌曲 object 使用了两个变量,但您可以根据需要添加任意多个变量。只需注意在创建"Song" object 实例时可以初始化哪个变量即可。示例中只能初始化歌曲名,播放的Bool默认初始化为false,是判断歌曲是否播放的变量。
然后您可以设置您的 Cell,在这种情况下,它将根据您的 "Song" 负责设置所有视图。为此,您可以为单元格创建一个自定义 class,在这种情况下,我只是将其命名为 Cell,您可以随意调用它。
class Cell : UICollectionViewCell {
var song : Song?
weak var cellDelegate : CellDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.backgroundColor = .red
}
func play() {
if !(song?.isPlaying)! {
song?.isPlaying = true
print("Playing", (song?.songName)!)
return
}
song?.isPlaying = false
print("Stop", (song?.songName)!)
}
}
请注意,单元格具有 play() 函数和一个名为 song var song : Song?
的变量。原因是我要让每个单元格决定何时播放或暂停。这会将单元格与您将在 collection 视图中使用的播放功能分离。
现在您有了 "Song" object,您可以轻松地在视图控制器或任何其他 class 中创建歌曲数组。例如:
var songArray : [Song] = [Song.init(songName: "Song One"), Song.init(songName: "Song Two"), Song.init(songName: "Song Three")]
最后,您可以使用歌曲数组中的每首歌曲初始化 collection 视图的每个单元格。为此,您将使用 cellForItemAt
函数:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.song = songArray[indexPath.item]
cell.cellDelegate = self
return cell
}
现在,您会看到单元格还有一个名为 var cellDelegate : CellDelegate
的弱变量,这是您将用来控制单元格播放和暂停功能的协议。您可以稍后进一步阅读有关委托的内容,以及它们如何帮助您尽可能地将您的单元与控制器分离。此外,另一个 collection 视图控制器可能符合此委托,您将拥有对单元格功能的相同访问权限,而无需重写所有代码。
协议可以在您的手机外设置 class:
protocol CellDelegate : class {
func didSelectCell (for cell: Cell)
}
最后,使视图控制器符合 CellDelegate:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, CellDelegate
现在播放、暂停、上一个、下一个等的代码变得更简单、更清晰
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentIndexPathItem = indexPath.item
let cellToPlay = collectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as! Cell
didSelectCell(for: cellToPlay)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cellToStop = collectionView.cellForItem(at: indexPath) as! Cell
if (cellToStop.song?.isPlaying)! { didSelectCell(for: cellToStop) }
}
@objc func previous () {
let playingCell = collectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as! Cell
if (playingCell.song?.isPlaying)! { didSelectCell(for: playingCell) }
if currentIndexPathItem - 1 < 0 { return }
else { currentIndexPathItem = currentIndexPathItem - 1 }
let cellToPlay = collectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as! Cell
didSelectCell(for: cellToPlay)
}
func didSelectCell(for cell: Cell) {
cell.play()
}
请注意 didSelectCell
函数,这是您需要添加的所有内容以符合委托并播放或暂停您的歌曲。就是这样。代码更简洁、更简单。