标签未使用 Swift 更新
Label does not update using Swift
我正在尝试改进我分叉的 GitHub 项目 (https://github.com/giacmarangoni/Swift-Radio-Pro/tree/xcode8)。
经过一些修复和更改后,一切似乎都运行良好,但突然间我注意到一个非常奇怪的行为。
当我第一次打开 "NowPlayingViewController" 并且电台开始流式传输时,一切正常,并且 AVPlayer 委托按预期更新用户界面(songLabel、titleLabel 和 albumArtwork)。
之后,在不停止广播流的情况下,我尝试返回 "StationsViewController" 并立即使用 "Now playing" 按钮重新打开 "NowPlayingViewController"。
此时委托仍然处于活动状态,流媒体仍在继续,但是当歌曲更改时,此视图控制器中的所有变量都会更新,但我不能对用户界面说同样的话。我尝试调试,发现标签已填充但未更新。 UI 主线程中的更新和 setNeedDisplay
没有帮助。
NowPlayingViewController
AVPlayer 设置:
func setUpPlayer(){
radioPlayer = Player.radio
radioPlayer.rate = 1
NotificationCenter.default.addObserver(
self,
selector: #selector(self.playerItemDidReachEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.radioPlayer.currentItem
)
}
在这里你可以找到 func onMetaData(_ metaData: [AVMetadataItem]?)
).
//*****************************************************************
// MARK: - AVPlayerItem Delegate (for metadata)
//*****************************************************************
extension NowPlayingViewController: CustomAVPlayerItemDelegate {
func onMetaData(_ metaData: [AVMetadataItem]?) {
if let metaDatas = metaData{
startNowPlayingAnimation()
let firstMeta: AVMetadataItem = metaDatas.first!
let metaData = firstMeta.value as! String
var stringParts = [String]()
if metaData.range(of: " - ") != nil {
stringParts = metaData.components(separatedBy: " - ")
} else {
stringParts = metaData.components(separatedBy: "-")
}
// Set artist & songvariables
let currentSongName = track.title
track.artist = stringParts[0].decodeAllChars()
track.title = stringParts[0].decodeAllChars()
if stringParts.count > 1 {
track.title = stringParts[1].decodeAllChars()
}
if track.artist == "" && track.title == "" {
track.artist = currentStation.stationDesc
track.title = currentStation.stationName
}
DispatchQueue.main.async {
if currentSongName != self.track.title {
if kDebugLog {
print("METADATA artist: \(self.track.artist) | title: \(self.track.title)")
}
// Update Labels
self.artistLabel.text = self.track.artist
self.songLabel.text = self.track.title
self.updateUserActivityState(self.userActivity!)
// songLabel animation
self.songLabel.animation = "zoomIn"
self.songLabel.duration = 1.5
self.songLabel.damping = 1
self.songLabel.animate()
// Update Stations Screen
self.delegate?.songMetaDataDidUpdate(self.track)
// Query API for album art
self.resetAlbumArtwork()
self.queryAlbumArt()
}
}
}
}
}
根据timedMetaData
关键路径在"CustomAVPlayerItem"中观察到此方法;每次 AVPlayer 元数据更改时都会触发它。 class 是 AVPlayerItem:
的子class
import MediaPlayer
import Foundation
protocol CustomAVPlayerItemDelegate {
func onMetaData(_ metaData:[AVMetadataItem]?)
}
//*****************************************************************
// Makes sure that observers are removed before deallocation
//*****************************************************************
class CustomAVPlayerItem: AVPlayerItem {
var delegate : CustomAVPlayerItemDelegate?
init(url URL:URL)
{
if kDebugLog {print("CustomAVPlayerItem.init")}
super.init(asset: AVAsset(url: URL) , automaticallyLoadedAssetKeys:[])
addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions.new, context: nil)
}
deinit{
if kDebugLog {print("CustomAVPlayerItem.deinit")}
removeObserver(self, forKeyPath: "timedMetadata")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let avpItem: AVPlayerItem = object as? AVPlayerItem {
if keyPath == "timedMetadata" {
delegate?.onMetaData(avpItem.timedMetadata)
}
}
}
}
以下是我的AVPlayer:
import MediaPlayer
//*****************************************************************
// This is a singleton struct using Swift
//*****************************************************************
struct Player {
static var radio = AVPlayer()
}
这是我用来打开"NowPlayingViewController".
StationsViewController
的segue函数
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "NowPlaying" {
self.title = ""
firstTime = false
let nowPlayingVC = segue.destination as! NowPlayingViewController
nowPlayingVC.delegate = self
if let indexPath = (sender as? IndexPath) {
// User clicked on row, load/reset station
if searchController.isActive {
currentStation = searchedStations[indexPath.row]
} else {
currentStation = stations[indexPath.row]
}
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
} else {
// User clicked on a now playing button
if let currentTrack = currentTrack {
// Return to NowPlaying controller without reloading station
nowPlayingVC.track = currentTrack
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = false
} else {
// Issue with track, reload station
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
}
}
}
}
以下是我认为您不理解的地方以及实际发生的事情。
通常,当您从推送视图控制器"go back" 时,推送视图控制器会弹出并销毁。您的推送视图控制器是 NowPlayingViewController。当您从它 "go back" 到 StationsViewController 时,它应该 destroyed。因此,当您再次 显示 NowPlayingViewController 时,您必须创建一个 新的、不同的 NowPlayingViewController。
好的,到目前为止一切顺利,前提是您了解所有这些。但是在 你的 情况下还有一个更复杂的问题:你有泄漏!您的旧 NowPlayingViewController 不会 被销毁。因此,当您 "go back" 到 StationsViewController 并第二次显示 NowPlayingViewController 时,现在有 两个 NowPlayingViewControllers — 您看到的新的和旧的漏水
好的,所以您的 logging 继续显示 old NowPlayingViewController,它仍在观察和更新。但是您的 眼睛 看到的是 new NowPlayingViewController,它什么也不做。这就解释了你所描述的现象。
如果这是正确的——而且,从你所说的,我很确定它是——那么你需要重新组织你的架构,这样你就不会得到这个泄漏,或者当你展示NowPlayingViewController 第二次显示 same NowPlayingViewController 而不是创建一个不同的 NowPlayingViewController。 (第一种方法会更好。)
我正在尝试改进我分叉的 GitHub 项目 (https://github.com/giacmarangoni/Swift-Radio-Pro/tree/xcode8)。
经过一些修复和更改后,一切似乎都运行良好,但突然间我注意到一个非常奇怪的行为。
当我第一次打开 "NowPlayingViewController" 并且电台开始流式传输时,一切正常,并且 AVPlayer 委托按预期更新用户界面(songLabel、titleLabel 和 albumArtwork)。
之后,在不停止广播流的情况下,我尝试返回 "StationsViewController" 并立即使用 "Now playing" 按钮重新打开 "NowPlayingViewController"。
此时委托仍然处于活动状态,流媒体仍在继续,但是当歌曲更改时,此视图控制器中的所有变量都会更新,但我不能对用户界面说同样的话。我尝试调试,发现标签已填充但未更新。 UI 主线程中的更新和 setNeedDisplay
没有帮助。
NowPlayingViewController
AVPlayer 设置:
func setUpPlayer(){
radioPlayer = Player.radio
radioPlayer.rate = 1
NotificationCenter.default.addObserver(
self,
selector: #selector(self.playerItemDidReachEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.radioPlayer.currentItem
)
}
在这里你可以找到 func onMetaData(_ metaData: [AVMetadataItem]?)
).
//*****************************************************************
// MARK: - AVPlayerItem Delegate (for metadata)
//*****************************************************************
extension NowPlayingViewController: CustomAVPlayerItemDelegate {
func onMetaData(_ metaData: [AVMetadataItem]?) {
if let metaDatas = metaData{
startNowPlayingAnimation()
let firstMeta: AVMetadataItem = metaDatas.first!
let metaData = firstMeta.value as! String
var stringParts = [String]()
if metaData.range(of: " - ") != nil {
stringParts = metaData.components(separatedBy: " - ")
} else {
stringParts = metaData.components(separatedBy: "-")
}
// Set artist & songvariables
let currentSongName = track.title
track.artist = stringParts[0].decodeAllChars()
track.title = stringParts[0].decodeAllChars()
if stringParts.count > 1 {
track.title = stringParts[1].decodeAllChars()
}
if track.artist == "" && track.title == "" {
track.artist = currentStation.stationDesc
track.title = currentStation.stationName
}
DispatchQueue.main.async {
if currentSongName != self.track.title {
if kDebugLog {
print("METADATA artist: \(self.track.artist) | title: \(self.track.title)")
}
// Update Labels
self.artistLabel.text = self.track.artist
self.songLabel.text = self.track.title
self.updateUserActivityState(self.userActivity!)
// songLabel animation
self.songLabel.animation = "zoomIn"
self.songLabel.duration = 1.5
self.songLabel.damping = 1
self.songLabel.animate()
// Update Stations Screen
self.delegate?.songMetaDataDidUpdate(self.track)
// Query API for album art
self.resetAlbumArtwork()
self.queryAlbumArt()
}
}
}
}
}
根据timedMetaData
关键路径在"CustomAVPlayerItem"中观察到此方法;每次 AVPlayer 元数据更改时都会触发它。 class 是 AVPlayerItem:
import MediaPlayer
import Foundation
protocol CustomAVPlayerItemDelegate {
func onMetaData(_ metaData:[AVMetadataItem]?)
}
//*****************************************************************
// Makes sure that observers are removed before deallocation
//*****************************************************************
class CustomAVPlayerItem: AVPlayerItem {
var delegate : CustomAVPlayerItemDelegate?
init(url URL:URL)
{
if kDebugLog {print("CustomAVPlayerItem.init")}
super.init(asset: AVAsset(url: URL) , automaticallyLoadedAssetKeys:[])
addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions.new, context: nil)
}
deinit{
if kDebugLog {print("CustomAVPlayerItem.deinit")}
removeObserver(self, forKeyPath: "timedMetadata")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let avpItem: AVPlayerItem = object as? AVPlayerItem {
if keyPath == "timedMetadata" {
delegate?.onMetaData(avpItem.timedMetadata)
}
}
}
}
以下是我的AVPlayer:
import MediaPlayer
//*****************************************************************
// This is a singleton struct using Swift
//*****************************************************************
struct Player {
static var radio = AVPlayer()
}
这是我用来打开"NowPlayingViewController".
StationsViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "NowPlaying" {
self.title = ""
firstTime = false
let nowPlayingVC = segue.destination as! NowPlayingViewController
nowPlayingVC.delegate = self
if let indexPath = (sender as? IndexPath) {
// User clicked on row, load/reset station
if searchController.isActive {
currentStation = searchedStations[indexPath.row]
} else {
currentStation = stations[indexPath.row]
}
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
} else {
// User clicked on a now playing button
if let currentTrack = currentTrack {
// Return to NowPlaying controller without reloading station
nowPlayingVC.track = currentTrack
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = false
} else {
// Issue with track, reload station
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
}
}
}
}
以下是我认为您不理解的地方以及实际发生的事情。
通常,当您从推送视图控制器"go back" 时,推送视图控制器会弹出并销毁。您的推送视图控制器是 NowPlayingViewController。当您从它 "go back" 到 StationsViewController 时,它应该 destroyed。因此,当您再次 显示 NowPlayingViewController 时,您必须创建一个 新的、不同的 NowPlayingViewController。
好的,到目前为止一切顺利,前提是您了解所有这些。但是在 你的 情况下还有一个更复杂的问题:你有泄漏!您的旧 NowPlayingViewController 不会 被销毁。因此,当您 "go back" 到 StationsViewController 并第二次显示 NowPlayingViewController 时,现在有 两个 NowPlayingViewControllers — 您看到的新的和旧的漏水
好的,所以您的 logging 继续显示 old NowPlayingViewController,它仍在观察和更新。但是您的 眼睛 看到的是 new NowPlayingViewController,它什么也不做。这就解释了你所描述的现象。
如果这是正确的——而且,从你所说的,我很确定它是——那么你需要重新组织你的架构,这样你就不会得到这个泄漏,或者当你展示NowPlayingViewController 第二次显示 same NowPlayingViewController 而不是创建一个不同的 NowPlayingViewController。 (第一种方法会更好。)