如何解决SwiftUI中搜索视图的@Binding参数传递限制?
问题描述
我有一个SongView,它通过@Binding属性包装器接收来自AlbumView的Song类型数据,目前该视图可以正常展示歌曲详情、添加收藏等功能。但在实现搜索功能时,我无法找到为SongView传递@Binding数据的方法,导致搜索列表无法直接跳转到选中的歌曲页面。请问是否有可行的解决办法,还是必须修改代码结构?
相关代码
SongView 文件
struct SongView: View { @Binding var song: Song @ObservedObject var songs: Songs let link = URL(string: "https://")! @State private var songDetails: Bool = false var body: some View { ZStack { Image("cover") .resizable().opacity(0.3) .zIndex(1) VStack { Image("collection") .resizable() .frame(width:300, height: 300) .padding(.bottom) HStack { Button { songs.isSongPlaying.toggle() song.isplaying.toggle() if songs.isSongPlaying { songs.playSongs(fileName: "\(song.name.lowercased()).mp3") } else { songs.audioPlayer?.stop() } } label: { Image(systemName: "play.circle") .renderingMode(.template) .font(.custom("Arial", size: 50)) } .padding() Button { song.isFavorite.toggle() } label: { Image(systemName: song.isFavorite ? "star.slash" : "star.circle") .renderingMode(.template) .font(.custom("Arial", size: 50)) } .padding() VStack { ShareLink(item: link) { Label("", systemImage: "square.and.arrow.up") .font(.custom("Arial", size: 50)) } } } .frame(width: 300) .overlay(RoundedRectangle(cornerRadius: 5) .stroke(.blue, lineWidth: 2) ) VStack { Button("Song Details") { songDetails = true } .font(.largeTitle) } .sheet(isPresented: $songDetails) { SongDetails() } } } } }
SearchView 文件
struct SearchView: View { @ObservedObject var songs: Songs @State private var searchText = "" var body: some View { NavigationView { List { ForEach(searchResults, id:\.self) { song in NavigationLink(destination: SongView(song: ??? , songs: Songs())) { Text(song) } } } .searchable(text: $searchText) .navigationTitle("Songs") } } var searchResults: [String] { if searchText.isEmpty { return songs.songs.map { $0.name } } else { return songs.songs.map { $0.name }.filter { $0.contains(searchText) } } } }
AlbumView 文件
struct AlbumView: View { @ObservedObject var songData: Songs var body: some View { ZStack { Image("cover") .resizable().opacity(0.5) .zIndex(1) ScrollView { VStack { ForEach($songData.songs) { $song in NavigationLink { SongView(song: $song, songs: songData) } label: { customText(image: "joinus", str: song.name) .frame(maxWidth: .infinity) } } }.padding() }.zIndex(2) } } func customText(image: String, str: String) -> some View { Group { VStack { RoundedRectangle(cornerRadius: 20) .fill(.blue) .padding() .frame(width: 300, height: 150) .overlay(Image(image) .resizable() .frame(width: 300, height: 150)) Text(str) .scaledFont(name: "Gothic", size: 20) .foregroundColor(.white) .padding() .background(RoundedRectangle(cornerRadius: 20, style: .continuous).fill(Color.gray)) } } } }
Songs 类
struct Song: Identifiable, Codable { var id = UUID() var name: String var album: String var isFavorite: Bool var genre: String var artist: String var isplaying: Bool init(name: String, album: String, isFavorite: Bool, genre: String, artist: String, isplaying: Bool) { self.name = name self.album = album self.artist = artist self.genre = genre self.isFavorite = isFavorite self.isplaying = isplaying } static let `default` = Song(name: "demo", album: "demo", isFavorite: false, genre: "demo", artist: "demo", isplaying: false) } class Songs: ObservableObject { @Published var audioPlayer: AVAudioPlayer? @Published var isSongPlaying: Bool = false var songsData: [Song] = [ Song(name: "demo", album: "demo", isFavorite: false, genre: "prayers", artist: "raghav", isplaying: false), Song(name: "demo", album: "demo", isFavorite: true, genre: "prayers", artist: "radha", isplaying: false), Song(name: "demo", album: "demo", isFavorite: false, genre: "prayers", artist: "hare", isplaying: false), Song(name: "demo", album: "demo", isFavorite: true, genre: "prayers", artist: "radhhari", isplaying: false), Song(name: "demo", album: "demo", isFavorite: false, genre: "prayers", artist: "radha govind", isplaying: false), ] @Published var songs = [Song]() { didSet { if let encoded = try? JSONEncoder().encode(songs) { UserDefaults.standard.set(encoded, forKey: "demosongs") } } } init(){ if let savedItems = UserDefaults.standard.data(forKey: "demosongs") { if let decodedItems = try? JSONDecoder().decode([Song].self, from: savedItems) { songs = decodedItems return } } songs = songsData } func playSongs(fileName: String) { if let path = Bundle.main.path(forResource: fileName, ofType: nil) { do { let url = URL(fileURLWithPath: path) _ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers) audioPlayer = try AVAudioPlayer(contentsOf: url) audioPlayer?.play() } catch { print(error.localizedDescription) } } } }
ContentView 文件
struct ContentView: View { @StateObject var songData = Songs() var body: some View { TabView { HomeView(songs: songData) .tabItem { Label("Home", systemImage: "music.note.house") } SearchView(songs: songData) .tabItem { Label("Search", systemImage: "magnifyingglass.circle") } FavoritesView(songs: songData) .tabItem { Label("Favorites", systemImage: "star.circle") } } // HStack { if songData.isSongPlaying { Button { songData.audioPlayer?.stop() songData.isSongPlaying.toggle() } label: { Image(systemName: "playpause") .renderingMode(.template) .font(.custom("Arial", size: 50)) } } } } }
解决方案
不需要修改整体代码结构,只需要调整SearchView的实现,就能正确传递@Binding给SongView:
步骤1:修改搜索结果返回完整的Song对象
把searchResults从返回歌曲名字的[String]改成返回完整Song实例的[Song],这样能拿到每个歌曲的唯一标识id:
var searchResults: [Song] { if searchText.isEmpty { return songs.songs } else { return songs.songs.filter { $0.name.contains(searchText) } } }
步骤2:通过索引获取Binding<Song>
在List的ForEach中,通过歌曲的id找到它在songs.songs数组中的索引,再利用数组的绑定下标$songs.songs[index]获取对应的Binding<Song>,同时传递已有的songs实例(不要创建新的Songs()):
List { ForEach(searchResults) { searchedSong in // 通过id找到对应歌曲在数据源中的索引 if let index = songs.songs.firstIndex(where: { $0.id == searchedSong.id }) { NavigationLink(destination: SongView(song: $songs.songs[index], songs: songs)) { Text(searchedSong.name) } } } }
原理说明
因为Songs中的songs是@Published数组,$songs.songs[index]会返回一个Binding<Song>,这个绑定直接关联到数据源中的对应歌曲实例,所以SongView中对song的修改(比如收藏状态、播放状态)会同步回全局的songs对象,和AlbumView中的行为保持一致。
内容的提问来源于stack exchange,提问作者multiverse
相关产品推荐
相关产品推荐

