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