You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 04:30:30