SwiftUI:在列表中切换按钮状态
SwiftUI: toggle buttons state inside List
我尝试使用曲目列表实现一个简单的播放器应用程序,其中每一行都有一个播放按钮,如果我为一首曲目按下播放,当前正在播放的曲目(如果有)应该停止播放(在本例中为播放按钮应该改变图标)。在这里我有一些问题 - 如何识别当前正在播放的曲目(播放按钮处于播放模式的位置),如何将其重置为初始状态以便一次只播放一首曲目。
在这个例子中,多个按钮可以切换到播放状态,而不是只需要一个按钮
import SwiftUI
class Audio: ObservableObject, Identifiable {
let id = UUID()
var title: String
@Published var isPlaying = false {
didSet {
print(isPlaying)
}
}
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
}
struct ListRow: View {
@ObservedObject var audio: Audio
var body: some View {
HStack {
Button(action: {
audio.isPlaying.toggle()
}) {
Image(systemName: audio.isPlaying ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
}
struct ContentView: View {
@ObservedObject var audiosFetcher = AudiosFetcher()
var body: some View {
List(audiosFetcher.audios, id: \.id) { audio in
ListRow(audio: audio)
}.onAppear {
audiosFetcher.fetchAudios()
}
}
}
更新:解决方案
感谢@Yrb 的回答,我们可以这样做了。也许 AudiosFetcher
不是保存当前播放音频的最佳位置,但它可以工作并且可以在需要时提取到单独的实体中
struct ContentView: View {
// The intialization of the ObservableObject should be a @StateObject,
// not an @ObservedObject.
@StateObject var audiosFetcher = AudiosFetcher()
var body: some View {
List($audiosFetcher.audios) { audio in
ListRow(audiosFetcher: audiosFetcher, audio: audio)
}
.onAppear {
audiosFetcher.fetchAudios()
}
}
}
struct ListRow: View {
@StateObject var audiosFetcher: AudiosFetcher
@Binding var audio: Audio
var body: some View {
HStack {
Button(action: {
audiosFetcher.playingAudio = (audiosFetcher.playingAudio == audio ? nil : audio)
}) {
Image(systemName: audiosFetcher.playingAudio == audio ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
}
struct Audio: Identifiable, Equatable {
let id = UUID()
var title: String
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
@Published var playingAudio: Audio?
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
}
您可以尝试像这样重置所有音频的初始状态,并且一次只播放一首曲目:
class Audio: ObservableObject, Identifiable {
let id = UUID()
var title: String
@Published var isPlaying = false {
didSet {
print(title + " isPlaying: \(isPlaying)")
}
}
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
// -- here
func toggleAudio(_ audio: Audio) {
let inState = audio.isPlaying
audios.forEach{ [=10=].isPlaying = false} // <-- turn all off
audio.isPlaying = !inState // <-- only toggle this one
}
}
struct ContentView: View {
@StateObject var audiosFetcher = AudiosFetcher() // <-- here
var body: some View {
List(audiosFetcher.audios, id: \.id) { audio in
ListRow(audio: audio)
}.onAppear {
audiosFetcher.fetchAudios()
}
.environmentObject(audiosFetcher) // <-- here
}
}
struct ListRow: View {
@EnvironmentObject var audiosFetcher: AudiosFetcher // <-- here
@ObservedObject var audio: Audio
var body: some View {
HStack {
Button(action: {
audiosFetcher.toggleAudio(audio) // <-- here
}) {
Image(systemName: audio.isPlaying ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
}
我修改了一些东西。首先,因为你只想要一个音频,最多,播放我把播放状态从数据模型中拉出来 Audio
。我还把它变成了一个结构体,因为这是 SwiftUI 的首选。我将播放状态移到了视图模型中。为了简化代码,我实际上删除了 ListRow
,尽管您可以将视图模型注入 ListRow
。这为您提供了播放音频的真实来源。如果没有播放音频,它将是 nil
。控件已关闭此值:
struct ContentView: View {
// The intialization of the ObservableObject should be a @StateObject,
// not an @ObservedObject.
@StateObject var audiosFetcher = AudiosFetcher()
var body: some View {
List($audiosFetcher.audios) { $audio in
HStack {
Button(action: {
audiosFetcher.isPlaying = (audiosFetcher.isPlaying == audio ? nil : audio)
}) {
Image(systemName: audiosFetcher.isPlaying == audio ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
.onAppear {
audiosFetcher.fetchAudios()
}
}
}
struct Audio: Identifiable, Equatable {
let id = UUID()
var title: String
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
@Published var isPlaying: Audio?
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
}
我尝试使用曲目列表实现一个简单的播放器应用程序,其中每一行都有一个播放按钮,如果我为一首曲目按下播放,当前正在播放的曲目(如果有)应该停止播放(在本例中为播放按钮应该改变图标)。在这里我有一些问题 - 如何识别当前正在播放的曲目(播放按钮处于播放模式的位置),如何将其重置为初始状态以便一次只播放一首曲目。
在这个例子中,多个按钮可以切换到播放状态,而不是只需要一个按钮
import SwiftUI
class Audio: ObservableObject, Identifiable {
let id = UUID()
var title: String
@Published var isPlaying = false {
didSet {
print(isPlaying)
}
}
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
}
struct ListRow: View {
@ObservedObject var audio: Audio
var body: some View {
HStack {
Button(action: {
audio.isPlaying.toggle()
}) {
Image(systemName: audio.isPlaying ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
}
struct ContentView: View {
@ObservedObject var audiosFetcher = AudiosFetcher()
var body: some View {
List(audiosFetcher.audios, id: \.id) { audio in
ListRow(audio: audio)
}.onAppear {
audiosFetcher.fetchAudios()
}
}
}
更新:解决方案
感谢@Yrb 的回答,我们可以这样做了。也许 AudiosFetcher
不是保存当前播放音频的最佳位置,但它可以工作并且可以在需要时提取到单独的实体中
struct ContentView: View {
// The intialization of the ObservableObject should be a @StateObject,
// not an @ObservedObject.
@StateObject var audiosFetcher = AudiosFetcher()
var body: some View {
List($audiosFetcher.audios) { audio in
ListRow(audiosFetcher: audiosFetcher, audio: audio)
}
.onAppear {
audiosFetcher.fetchAudios()
}
}
}
struct ListRow: View {
@StateObject var audiosFetcher: AudiosFetcher
@Binding var audio: Audio
var body: some View {
HStack {
Button(action: {
audiosFetcher.playingAudio = (audiosFetcher.playingAudio == audio ? nil : audio)
}) {
Image(systemName: audiosFetcher.playingAudio == audio ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
}
struct Audio: Identifiable, Equatable {
let id = UUID()
var title: String
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
@Published var playingAudio: Audio?
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
}
您可以尝试像这样重置所有音频的初始状态,并且一次只播放一首曲目:
class Audio: ObservableObject, Identifiable {
let id = UUID()
var title: String
@Published var isPlaying = false {
didSet {
print(title + " isPlaying: \(isPlaying)")
}
}
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
// -- here
func toggleAudio(_ audio: Audio) {
let inState = audio.isPlaying
audios.forEach{ [=10=].isPlaying = false} // <-- turn all off
audio.isPlaying = !inState // <-- only toggle this one
}
}
struct ContentView: View {
@StateObject var audiosFetcher = AudiosFetcher() // <-- here
var body: some View {
List(audiosFetcher.audios, id: \.id) { audio in
ListRow(audio: audio)
}.onAppear {
audiosFetcher.fetchAudios()
}
.environmentObject(audiosFetcher) // <-- here
}
}
struct ListRow: View {
@EnvironmentObject var audiosFetcher: AudiosFetcher // <-- here
@ObservedObject var audio: Audio
var body: some View {
HStack {
Button(action: {
audiosFetcher.toggleAudio(audio) // <-- here
}) {
Image(systemName: audio.isPlaying ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
}
我修改了一些东西。首先,因为你只想要一个音频,最多,播放我把播放状态从数据模型中拉出来 Audio
。我还把它变成了一个结构体,因为这是 SwiftUI 的首选。我将播放状态移到了视图模型中。为了简化代码,我实际上删除了 ListRow
,尽管您可以将视图模型注入 ListRow
。这为您提供了播放音频的真实来源。如果没有播放音频,它将是 nil
。控件已关闭此值:
struct ContentView: View {
// The intialization of the ObservableObject should be a @StateObject,
// not an @ObservedObject.
@StateObject var audiosFetcher = AudiosFetcher()
var body: some View {
List($audiosFetcher.audios) { $audio in
HStack {
Button(action: {
audiosFetcher.isPlaying = (audiosFetcher.isPlaying == audio ? nil : audio)
}) {
Image(systemName: audiosFetcher.isPlaying == audio ? "pause.circle" : "play.circle")
}
.buttonStyle(BorderlessButtonStyle())
.font(.largeTitle)
Text(audio.title)
}
}
.onAppear {
audiosFetcher.fetchAudios()
}
}
}
struct Audio: Identifiable, Equatable {
let id = UUID()
var title: String
init(title: String) {
self.title = title
}
}
class AudiosFetcher: ObservableObject {
@Published var audios = [Audio]()
@Published var isPlaying: Audio?
func fetchAudios() {
audios = [
Audio(title: "track 1"),
Audio(title: "track 2"),
Audio(title: "track 3")
]
}
}