How to display sorted map from Firebase - swift

I am making audio streaming app. I have already made a list of albums with songs attached to each album. I can sort these albums by 'order by' function, but I don't know how to sort the song array. In firebase I've made one main collection called Album and inside of this collection I've made few string files, and one them is map called song which consists of another few string files. And my problem is that I can't sort the songs in swiftui project.
This is audio data code:
import Foundation
import SwiftUI
import FirebaseFirestore
import Firebase
class OurData: ObservableObject {
#Published public var albums = [Album]()
func loadAlbums() {
Firestore.firestore().collection("Album").order(by: "songs", descending: true)
Firestore.firestore().collection("Album").getDocuments { (snapshot, error) in
if error == nil {
for document in snapshot!.documents{
let name = document.data()["name"] as? String ?? "error"
let image = document.data()["image"] as? String ?? "1"
let songs = document.data()["songs"] as? [String : [String : Any]]
var songsArray = [Song]()
if let songs = songs{
for song in songs {
let songName = song.value["name"] as? String ?? "error"
let songTime = song.value["time"] as? String ?? "error"
let artistName = song.value["artistName"] as? String ?? "error"
let songFile = song.value["file"] as? String ?? "error"
songsArray.append(Song(songName: songName, image: image, time: songTime, artist: artistName, file: songFile))
}
}
self.albums.append(Album(albumName: name, image: image, song: songsArray))
}
}else{
print(error!)
}
}
}
Here's audio player code:
import SwiftUI
struct Album : Hashable {
var ID = UUID()
var albumName : String
var image : String
var song : [Song]
}
struct Song: Hashable {
var ID = UUID()
var songName : String
var image : String
var time : String
var artist : String
var file : String
}
struct PodcastView : View {
#ObservedObject var data: OurData
#State private var currentAlbum : Album?
var body: some View {
VStack{
ScrollView (.horizontal, showsIndicators: false, content: {
LazyHStack{
ForEach(self.data.albums, id: \.self, content: {
album in
AlbumView(album: album).onTapGesture {
self.currentAlbum = album
}
})
}
})
ScrollView{
LazyVStack{
if self.data.albums.first == nil {
EmptyView()
}else{
ForEach((self.currentAlbum?.song ?? self.data.albums.first?.song) ?? [Song(songName: "", image: "", time: "", artist: "", file: "")],
id: \.self,
content: {
song in
SongCell(album: currentAlbum ?? self.data.albums.first!, song: song)
})
}
}
}
.navigationTitle("Podcasty")
.foregroundColor(.black)
}
}
}
struct AlbumView : View{
var album : Album
var body: some View{
ZStack (alignment: .bottom, content: {
Image(album.image)
.resizable()
.aspectRatio(contentMode: .fill)
ZStack{
Blur(style: .extraLight)
Text(album.albumName)
.foregroundColor(.black)
}.frame(height: 60, alignment: .center)
}).frame(width: 200, height: 200, alignment: .center)
.cornerRadius(15.0)
.shadow(radius: 10)
.padding(15)
}
}
struct SongCell : View {
#State var showPlayer = false
var album : Album
var song : Song
var body: some View {
Button(action: {
self.showPlayer.toggle()
}){
HStack{
Image(song.image)
.resizable()
.frame(width: 100, height: 100)
.cornerRadius(15)
.padding()
VStack{
Text(song.songName)
.fontWeight(.semibold)
.font(.system(size: 18))
Text(song.artist)
.font(.system(size: 16))
}
Spacer()
Text(song.time)
.padding(.trailing, 10)
}.frame(width: 380, height: 120, alignment: .trailing)
.background(
ZStack {
RoundedRectangle(cornerRadius: 25)
.fill(Color.white)
.shadow(color: Color(#colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)), radius: 5, x: 0, y: 5)
})
.padding(10)
}.sheet(isPresented: $showPlayer, content: {
PodcastPlayerView(album: album, song: song)
})
}
}
This is link to screenshot of my Firebase album and song data:
https://firebasestorage.googleapis.com/v0/b/kosciolfiladelfia-6b832.appspot.com/o/zdj%2FZrzut%20ekranu%20(130).png?alt=media&token=78b51b60-62e9-4935-95f9-18e8a1dc6610

Related

How to add a selected value from cloudKit and display it in a view (SwiftUI)

I'm currently working on an app that has a function that adds a selected country from a search list retrieved from CloudKit. I got the search to work and the list to show and everything but I'm stuck at the part where the selected country will be added to the list.
To clarify my goal, I want this function to work similarly to the weather app on iPhone.
In order to add a city to your saved counties, you need to search the country and then select it to add it to the list of saved countries. I will attach some images at the end of the page for extra clarification.
I'm new to this whole Swift thing and I'm trying to sharpen my skills in it. I tried to look for documentation regarding this very thing without success. I would appreciate some help.
This is my code
import SwiftUI
import CloudKit
struct addCity: View {
#State private var searchText = ""
#State private var showingNatio = false
#State private var showingNotifi = false
#State private var country = ""
#State var em :[Emergency] = []
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var selectCountry: FetchedResults<CountriesList>
#State private var shoeingAddscreen = false
var body: some View {
VStack() {
NavigationStack {
VStack{
List{
ForEach(selectCountry, id: \.self){ cont in
Text("Name \(selectCountry.count)")
city(cityPic: "france" , cityName: cont.country)}
}.scrollContentBackground(.hidden)
}
Spacer()
.navigationTitle("Countries")
.font(.system(size: 30))
.toolbar{
ToolbarItemGroup(placement: .navigationBarTrailing){
Menu {
Section {
Button(action: {}) {
Label("Edit List", systemImage: "pencil")
}
Button(action: {
showingNatio.toggle()
}) {
Label("Edit Nationality", systemImage: "globe.asia.australia.fill")
}
Button(action: {
showingNotifi.toggle()
}) {
Label("Notification", systemImage: "bell.badge")
}
}
}label: {
Image(systemName: "ellipsis.circle")
.resizable()
.scaledToFit()
.frame(width: 22)
.frame(maxWidth: 330, alignment: .trailing)
.padding(.top)
}
}//ToolbarItemGroup
}//toolbar
//.searchable(text: $searchText)
}.searchable(text: $searchText) {
ForEach(array) { emergency in
//Text(emergency.Country).searchCompletion(emergency)
HStack{
Text(emergency.Country).searchCompletion(emergency)
Spacer()
Button {
var slected = emergency.Country
let cont = CountriesList(context: moc)
cont.country = emergency.Country
try? moc.save()
} label: {
Text("Add")
.foregroundColor(Color.blue)
}
}
.padding(.horizontal).frame(maxWidth: 390)
}
// NavigationStack
}.onAppear{
fetchEvent()
}
}.sheet(isPresented: $showingNatio) {
Nationality()
}.sheet(isPresented: $showingNotifi) {
Notification()
}
}//vstack
var array : [Emergency]{
searchText.isEmpty ? em : em.filter{$0.Country.contains(searchText)
}
}
func fetchEvent(){
em.removeAll()
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType:"Emergency", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordMatchedBlock = {recordID, result in
switch result{
case .success(let record):
let emer = Emergency(record: record)
em.append(emer)
case .failure(let error):
print("Error:\(error.localizedDescription)")
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
func fetchSpecific(){
em.removeAll()
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType:"Emergency", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordMatchedBlock = {recordID, result in
switch result{
case .success(let record):
let emer = Emergency(record: record)
em.append(emer)
case .failure(let error):
print("Error:\(error.localizedDescription)")
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
}
struct addCity_Previews: PreviewProvider {
static var previews: some View {
addCity()
}
}
struct city: View {
#State var cityPic = "france"
#State var cityName = ""
#State private var country = ""
#State var em :[Emergency] = []
//#FetchRequest(sortDescriptors: []) var countries: FetchedResults <CountryList>
#Environment(\.managedObjectContext) var moc
var body: some View {
ZStack (alignment: .leading){
Image(cityPic)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 360, height: 100)
.cornerRadius(7)
.overlay( Rectangle()
.foregroundColor(.black)
.cornerRadius(7)
.opacity(0.4))
Text(cityName)
.foregroundColor(.white)
.bold()
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
.foregroundColor(.white)
.padding()
}.padding(.horizontal,40)
}
}
struct Emergency: Identifiable{
let record: CKRecord
let Country: String
let id: CKRecord.ID
init(record: CKRecord){
self.record = record
self.id = record.recordID
self.Country = record["Country"] as? String ?? ""
}
}
Here the user seraches throught the cloud then selcts the country to add it.
Here the selected country will be added to the list

SwiftUI Sending Parameters To SecondView

Hello there i am trying to learn SwiftUI and my version is 16.0 i have a problem with transferring data between two pages. My situation is i want a list which contains my elements like : Image, Text etc. then when user clicks to an item. I want to send him or her detail page which gives selected item : Image, Text. I hope i clarified my problem with my explanation.
First of all Here is my list and list item :
struct WebViewCard: View {
#EnvironmentObject var dataManager : DataManager
#Environment(\.dismiss) var dismiss
var body: some View {
List {
ForEach(dataManager.zodiacNews, id: \.id){ zodiacNews in
HStack(alignment: .top) {
VStack(alignment: .leading,content: {
WebImage(url: URL(string: zodiacNews.image))
.resizable()
.scaledToFit()
.frame(width: 300, height: 170)
.cornerRadius(10)
.shadow(radius: 10)
VStack(alignment: .leading, spacing: 5) {
Text(zodiacNews.title)
.foregroundColor(.primary)
.font(.headline)
Text(zodiacNews.subTitle)
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.lineLimit(2)
.frame(height: 40)
}
}).shadow(color: Color(red:0,green: 0,blue: 0,opacity: 0.3), radius: 3,x: 6,y: 6)
}
}
}.background {
Color.white
}.navigationTitle("Burçlar")
}
}
Second page is where i am putting this view :
struct OpenWebPageList: View {
#EnvironmentObject var dataManager : DataManager
var body: some View {
let allNews = self.dataManager.zodiacNews
NavigationView {
NavigationLink(destination: OpenWebPage(news: allNews)){
WebViewCard()
}
}
}}
Third one will be My Detail View ->
struct OpenWebPage: View {
var news : [BurcWebViewDataClass]
var body: some View {
ForEach(news) { news in
Text(news.title)
}
}}
and finally my datamanager code is below:
class DataManager : ObservableObject {
#Published var zodiacs : [ZodiacData] = []
#Published var zodiacNews : [BurcWebViewDataClass] = []
init(){
fetchZodiacNews()
}
func fetchZodiacNews(){
zodiacNews.removeAll()
let db = Firestore.firestore()
let ref = db.collection("BurcHaberleri")
ref.getDocuments{ snapshot, error in
guard error == nil else {
print(error!.localizedDescription)
return
}
if let snapshot = snapshot{
for document in snapshot.documents{
let data = document.data()
let id = data["id"] as? String ?? ""
let url = data["url"] as? String ?? ""
let image = data["image"] as? String ?? ""
let title = data["title"] as? String ?? ""
let subTitle = data["subTitle"] as? String ?? ""
let zodiacNews = BurcWebViewDataClass(id: id, url: url, image: image, title: title, subTitle: subTitle)
self.zodiacNews.append(zodiacNews)
}
}
}
}
with this coode if user clicks on any item which is in the list. User will see all the items from my firebase-firestore. So i want to resolve this problem. Any help will be preciated.

Check box with the data taken from the database - Swift - SwiftUi - Firebase

I am creating a section within my app where it will be possible to see the list of users and their permissions (inserted in the form of an array) inserted into the database.
I view the user's information and permissions in this way:
List(administratorManager.users) { user in
HStack {
VStack(alignment: .leading) {
Text(user.number).font(.subheadline)
Text(user.name).font(.subheadline)
Text(user.surname).font(.subheadline)
VStack(alignment: .leading){
Text("Permessi")
.font(.title2)
.fontWeight(.bold)
.padding(1)
HStack{
Image(systemName: user.permessi[0] ? "checkmark.square.fill" : "square")
.foregroundColor(user.permessi[0] ? Color(UIColor.systemBlue) : Color.secondary)
.onTapGesture {
user.permessi[0].toggle()
}
Text("0")
.onTapGesture {
user.permessi[0].toggle()
}
}
HStack{
Image(systemName: user.permessi[1] ? "checkmark.square.fill" : "square")
.foregroundColor(user.permessi[1] ? Color(UIColor.systemBlue) : Color.secondary)
.onTapGesture {
user.permessi[1].toggle()
}
Text("1")
.onTapGesture {
user.permessi[0].toggle()
}
}
}
}
}
}
.onAppear() {
self.administratorManager.fetchData(collection: "Admins")
}
The data is constantly read from the database in this way and saved in a structure:
func fetchData(collection: String) {
DatabaseFirestore.collection(collection).addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.users = documents.map { (queryDocumentSnapshot) -> User in
let data = queryDocumentSnapshot.data()
let name = data["Nome"] as? String ?? ""
let surname = data["Cognome"] as? String ?? ""
let school = data["Scuola"] as? String ?? ""
let orari = data["Orari"] as? [String] ?? []
let permessi = data["Permessi"] as? [Bool] ?? []
let number = queryDocumentSnapshot.documentID
return User(name: name, surname: surname, school: school, orari: orari, permessi: permessi, number: number)
}
}
}
This is the structure:
struct User: Identifiable {
var id: String = UUID().uuidString
var name: String
var surname: String
var school: String
var orari: [String]
var permessi: [Bool]
var number: String
}
The data is correctly displayed like this:
Phone of the screen
Photo of the database
The problem arises when the checkbox is pressed. when this is added I am told that user is a let constant:
.onTapGesture {
user.permessi[0].toggle()
}
How could I change the code to make it work?
Also I tried to shorten the code through a forEach:
ForEach(user.permessi) p in {
HStack{
Image(systemName: p ? "checkmark.square.fill" : "square")
.foregroundColor(p ? Color(UIColor.systemBlue) : Color.secondary)
.onTapGesture {
}
Text("0")
.onTapGesture {
}
.padding(1)
}
}
I know it is wrong but no error message is displayed. How do I resolve?
To be able to mutate a value, the object and the element must be var. But the object it let.
UPDATE:
I tested it and the following approach works for me.
Please keep in mind, the issue in your question is SwiftUI related and not firebase related, so instead of the firebase storage I am using some local data, but the array of User elements should be identical/similar and the implementation of the usage is the same.
In my example the UserViewModel is what you name the AdministratorManager. But even your AdministratorManager class should be an ObservableObject and the users array must contain #Published.
//
// ContentView.swift
// SwiftTest
//
// Created by Sebastian on 11.08.22.
//
import SwiftUI
struct ContentView: View {
#StateObject var userViewModel = UserViewModel()
var body: some View {
VStack() {
UserView(userViewModel: userViewModel)
}
}
}
struct UserView: View {
#ObservedObject var userViewModel: UserViewModel
var body: some View {
VStack(){
ForEach(userViewModel.users.indices, id: \.self){ id in
VStack(alignment: .leading) {
Text(userViewModel.users[id].number).font(.subheadline)
Text(userViewModel.users[id].name).font(.subheadline)
Text(userViewModel.users[id].surname).font(.subheadline)
ForEach(userViewModel.users[id].permessi.indices, id: \.self){ idp in
Button(action: {
userViewModel.users[id].permessi[idp].toggle()
}) {
Image(systemName: userViewModel.users[id].permessi[idp] ? "checkmark.square.fill" : "square")
.foregroundColor(userViewModel.users[id].permessi[idp] ? Color(UIColor.systemBlue) : Color.secondary)
}
}
}
.padding()
}
}
}
}
struct User: Identifiable, Hashable {
var id: String = UUID().uuidString
var name: String
var surname: String
var school: String
var permessi: [Bool]
var number: String
}
class UserViewModel: ObservableObject {
#Published var users = [
User(name: "Sebastian",
surname: "F",
school: "School",
permessi: [false, false, false],
number: "1"),
User(name: "Mickele",
surname: "M",
school: "School",
permessi: [true, true, true],
number: "2")
]
}
Best, Sebastian

Calling .resizable() on an Image makes the image disappear when pulling data from Firestore

Just to give you a bit of context, I'm creating a gallery view that uses the WaterfallGrid library. When I'm using mock data, the gallery looks fine.
struct Gallery: View {
#ObservedObject var galleryViewModel = GalleryViewModel()
#State var pictureData = [
PictureModel(id: "id1", title: "Traditional Clothing", image_uri: "traditional", photographer: "John Doe", body: ""),
PictureModel(id: "id2", title: "River", image_uri: "mountain_1", photographer: "John Doe", body: ""),
PictureModel(id: "id3", title: "Boat", image_uri: "mountain_2", photographer: "John Doe", body: "")
]
var body: some View {
ZStack {
Color("primary")
VStack {
HStack {
Text("Gallery")
.font(.system(size: 40, weight: .regular))
.foregroundColor(.white)
Spacer(minLength: 0)
}
.padding(.horizontal)
WaterfallGrid($pictureData) { $image in
NavigationLink(destination: PictureInfo(selectedImage: $image), label: {
Image(image.image_uri)
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(5)
})
}
.gridStyle(
columns: 2
)
}
}
}
init() {
galleryViewModel.getGallery()
}
}
But when I do $galleryViewModel.list instead of using the mock data, the gallery is empty. When I remove the .resizable() call on the Image, the picture appears fine.
import Foundation
import Firebase
import SwiftUI
class GalleryViewModel: ObservableObject {
#Published var list = [PictureModel]()
private let db = Firestore.firestore()
func getGallery() {
db.collection("gallery").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("no gallery")
return
}
self.list = documents.map { (queryDocumentSnapshot) in
let data = queryDocumentSnapshot.data()
let id = queryDocumentSnapshot.documentID
let title = data["title"] as? String ?? ""
let body = data["body"] as? String ?? ""
let image_uri = data["image_uri"] as? String ?? ""
let photographer = data["photographer"] as? String ?? ""
return PictureModel(id: id, title: title, image_uri: image_uri, photographer: photographer, body: body)
}
}
}
}
Any idea why it's doing this?
This seems to be an issue with the WaterfallGrid library rather than my code here. If there are less than 3 elements, it does not display anything. There is an Issue created on the Github page.
Adding .fixedSize(horizontal: false, vertical: true) inside the WaterfallGrid fixed it for me.

Cant get my sheet to display the right information

/*
I have an .onTapGesture on my MapAnnottions. If I press the MapAnnotations for the first time a
black/empty DisplayBusinessSheet will appear. I want it to display a certain type of
information. If I dismiss the sheet and press the same annotation I only get a black/empty
sheet. If I dismiss the sheet again and press another location/ MapAnnotation in my map it
displays the right information. I reallyyyyyy don't know what's happening, been stuck for solongggg, please help me <3
*/
import Foundation
import SwiftUI
import MapKit
import Firebase
import FirebaseFirestoreSwift
struct MapView: View {
#EnvironmentObject var firebaseModel: FirebaseModel
#EnvironmentObject var userData: UserData
#State var region: MKCoordinateRegion
#State var businessSheetPresented = false
#State var pressedLocation: Location? = nil
#State var pressedUser: User? = nil
var locationModel = LocationModel()
var body: some View {
VStack{
if let firebaseListOfLocations = firebaseModel.listOfLocations {
Map(coordinateRegion: $region,
showsUserLocation: true,
annotationItems: firebaseListOfLocations) { location in
//Every place has a marker
//anchorPoint is where we attatch the coordinates to the annotation
MapAnnotation(coordinate: location.userLocation!.coordinate, anchorPoint: CGPoint(x: 0.5, y: 0.5)) {
Image(systemName: "mappin")
.resizable()
.foregroundColor(ColorManager.darkPink)
.frame(width: 11, height: 30)
.onTapGesture(count: 1, perform: {
self.pressedLocation = location.userLocation!
self.pressedUser = location
print("Location name: \(location.userLocation!.id)")
print("Pressed Location name: \(pressedLocation)")
print("Pressed USer Name \(pressedUser!.name)")
print("Pressed USer Name \(pressedUser!)")
businessSheetPresented = true
})
}
}.ignoresSafeArea()
}
}.sheet(isPresented: $businessSheetPresented) {
if let pressedUser = pressedUser {
DisplayBusinessSheet(user: pressedUser)
}
}
.onAppear{
locationModel.askForPermission()
addUserCollectionListener()
print("LOCATIONS: \(firebaseModel.listOfLocations!.count)")
}
}
private func addUserCollectionListener(){
if let currentUserData = userData.userDocRef {
currentUserData.addSnapshotListener{ documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
try! self.userData.currUserData = document.data(as: UserDataModel.self)
}
}
}
}
//Here is the sheet I'm trying to present
import Foundation
import SwiftUI
struct DisplayBusinessSheet: View {
#State var user: User
private let certefiedTitle = "Certifierad"
private let myProductsTitle = "Mina Produkter"
private let aboutMeTitle = "Lite om mig"
private let socialMediaTitle = "Instagram"
let notYetPostedInfo = "Information is under cunstroction"
let heightFourth = UIScreen.main.bounds.height/4
var body: some View {
ZStack {
VStack(alignment: .leading){
ScrollView {
Image("nailimage")
.resizable()
.frame(height: heightFourth)
.ignoresSafeArea(edges: .top)
if let businessUser = user.businessUser {
Text("\(user.name)")
.foregroundColor(ColorManager.darkPink)
.fontWeight(.bold)
.font(.system(size: 20))
.padding()
TitleText(
title: certefiedTitle, textImage: Image(systemName: "link"),
textContent: businessUser.certifiedIn)
.padding(.bottom, 4)
TitleText(
title: myProductsTitle, textImage: Image(systemName: "wand.and.stars"),
textContent: businessUser.productType)
.padding(.bottom, 4)
TitleText(
title: aboutMeTitle, textImage: Image(systemName: "heart.text.square"),
textContent: businessUser.aboutMe)
.padding(.bottom, 4)
TitleText(
title: socialMediaTitle, textImage: Image(systemName: "link"),
textContent: businessUser.socialMedia)
.padding(.bottom, 4)
}
}.frame(height: heightFourth * 3)
}.onAppear{
print("Display Business User Name: \(user.name)")
print("Display Business User Certefied: \(user.businessUser!.certifiedIn)")
print("Display Business User Product type: \(user.businessUser!.productType)")
print("Display Business User About me: \(user.businessUser!.aboutMe)")
print("Display Business User Social media: \(user.businessUser!.socialMedia)")
}
}
}
}
In iOS 14 sheet(isPresented:content:) is now created beforehand and the view is not refreshed when the $businessSheetPresented changes.
Instead of .sheet(isPresented:content:)
.sheet(isPresented: $businessSheetPresented) {
if let pressedUser = pressedUser {
DisplayBusinessSheet(user: pressedUser)
}
}
you should use .sheet(item:content:)
.sheet(item: $pressedUser) { user in
DisplayBusinessSheet(user: pressedUser)
}
Note: with the above solution you don't really need businessSheetPresented unless it's used in some other place apart from triggering the sheet.