Home > Blockchain >  How can I overcome a @Binding restriction
How can I overcome a @Binding restriction

Time:09-27

I have a SongView, which has a @Binding propertywrapper , that gets a Song Type from album view, now in this View I can show details of Song , add favourite etc , all works well ..

The problem comes when I try and implement search , there I cannot seem to find a way to provide the @Binding data to be forwarded to the SongView, so the search List can lead directory to the song selected, is there any way out of it possible, or I will have to change the structure of my code , thanks

SongView File

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()
                }
            }

        }
 
    }
   
}

My search View

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) }
        }
    }
    
    
}

My Album View

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))
            }
        }
    }
}

My Songs Class

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)
            }
        }
            
    }
}

// Code where I use searchview

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))
                }
         
            }
        }
       
    }
    
}

CodePudding user response:

The issue here is you are trying to create multiple sources of truth. SwiftUI is discouraging this approach.

It is better to still use the @Published var you got in your Viewmodel and just show only the values you want to show.

struct SearchView: View {
    @ObservedObject var songs: Songs
    @State private var searchText = ""

    var body: some View {
        NavigationView {
            List {
                let results = searchResults
                ForEach($songs.songs) { $song in
                    if results.contains(where: {$0.id == song.id}){
                        NavigationLink(destination: SongView(song: $song, songs: songs)) {
                            Text(song.name)
                        }
                    }
                }
               
            }
            .searchable(text: $searchText)
            .navigationTitle("Songs")
           
        }
    }
    
    var searchResults: [Song] {
        if searchText.isEmpty {
            return  songs.songs
        } else {
            return songs.songs.filter { $0.name.contains(searchText) }
        }
    }
    
    
}
  • Related