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.
Related
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
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.
This is my first SwiftUI project and am very new in programming.
I have a View Model and a picker for my Content View, manage to send $selection to Detail View but not sure how to get it to read from the Firestore again. I kinda feel like something is missing, like I need to use an if-else, but can't pinpoint exactly what is the problem. Searched through the forum here but can't seemed to find a solution.
Here is the VM
import Foundation
import Firebase
class FoodViewModel: ObservableObject {
#Published var datas = [Food]()
private var db = Firestore.firestore()
func fetchData(){
db.collection("meals").addSnapshotListener { (snap, err) in
DispatchQueue.main.async {
if err != nil {
print((err?.localizedDescription)!)
return
} else {
for i in snap!.documentChanges{
let id = i.document.documentID
let name = i.document.get("name") as? String ?? ""
let weight = i.document.get("weight") as? Int ?? 0
let temp = i.document.get("temp") as? Int ?? 0
let time = i.document.get("time") as? Int ?? 0
self.datas.append(Food(id: id, name: name, weight: weight, temp: temp, time: time))
}
}
}
}
}
}
struct ContentView: View {
#ObservedObject var foodDatas = FoodViewModel()
#State private var id = ""
#State public var selection: Int = 0
var body: some View {
NavigationView{
let allFood = self.foodDatas.datas
ZStack {
Color("brandBlue")
.edgesIgnoringSafeArea(.all)
VStack {
Picker(selection: $selection, label: Text("Select your food")) {
ForEach(allFood.indices, id:\.self) { index in
Text(allFood[index].name.capitalized).tag(index)
}
}
.onAppear() {
self.foodDatas.fetchData()
}
Spacer()
NavigationLink(
destination: DetailView(selection: self.$selection),
label: {
Text("Let's make this!")
.font(.system(size: 20))
.fontWeight(.bold)
.foregroundColor(Color.black)
.padding(12)
})
}
And after the picker selected a food type, I hope for it to display the rest of details such as cooking time and temperature. Now it is displaying 'index out of range' for the Text part.
import SwiftUI
import Firebase
struct DetailView: View {
#ObservedObject var foodDatas = FoodViewModel()
#Binding var selection: Int
var body: some View {
NavigationView{
let allFood = self.foodDatas.datas
ZStack {
Color("brandBlue")
.edgesIgnoringSafeArea(.all)
VStack {
Text("Cooking Time: \(allFood[selection].time) mins")
}
}
}
}
Appreciate for all the help that I can get.
In your DetailView, you're creating a new instance of FoodViewModel:
#ObservedObject var foodDatas = FoodViewModel()
That new instance has not fetched any data, and thus the index of the selection is out of bounds, because its array is empty.
You could pass your original ContentView's copy of the FoodDataModel as a parameter.
So, the previous line I quoted would become:
#ObservedObject var foodDatas : FoodViewModel
And then your NavigationLink would look like this:
NavigationLink(destination: DetailView(foodDatas: foodDatas, selection: self.$selection) //... etc
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
I am building a very simple SwiftUI App, following a tutorial.
I created a View that contains a list of items which is #fetched from CoreData, and the list is shown with no problem.
Then I added a modal window with a TextField and a Button to save the data.
This is the code of the modal:
import SwiftUI
struct AddCategoryView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Environment(\.managedObjectContext) var context
#State public var name: String = ""
#State public var id = UUID()
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Close")
}
Text("Add a new Category")
.font(.largeTitle)
TextField("Enter category name", text: $name)
.padding(.all)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
let category = Category(context: self.context)
category.name = self.name
category.id = self.id
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
}) {
Text("SAVE \(self.name)")
}
}
}
}
struct AddCategoryView_Previews: PreviewProvider {
static var previews: some View {
AddCategoryView()
}
}
In this line,
Text("SAVE \(self.name)")
I am printing (for debugging) the value of the variable name, and I can see that the variables changes to the value that is in the catch statement
self.name = "There is an error!"
So I know that the saving is failing. But I have no idea why.
In the List View I have a button that will open this modal; I changed the value of this Button from
self.showAddModal.toggle()
TO
let category = Category(context: self.context)
category.name = "New Category"
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
And it works! I can see that the List View is updated with the value.
This is the code for the List View
struct CategoriesView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: Category.entity(), sortDescriptors: []) var categories: FetchedResults<Category>
#State private var showAddModal = false;
#State public var name = ""
var body: some View {
VStack {
Text("\(name)")
List {
ForEach(categories, id: \.id) { category in
VStack {
Text(category.name ?? "Unknown")
}
}
}
Button(action: {
let category = Category(context: self.context)
category.name = "New Category"
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
}) {
Text("+")
.font(.system(.largeTitle))
.fontWeight(.bold)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.frame(width: 48, height: 48)
.background(Color.purple)
.cornerRadius(24)
.padding(.all)
.shadow(color: Color.black.opacity(0.3),
radius: 3,
x: 3,
y: 3)
}.sheet(isPresented: $showAddModal) {
AddCategoryView()
}
}
}
}
struct CategoriesView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return CategoriesView().environment(\.managedObjectContext, context)
}
}
I have spent the last 2 hours looking and googling for a solution, but I cannot find out what is wrong in my code. I also tried on the simulator but got the same error; I cannot save the core data from the TextField.
Thanks in advance!
Ok I finally found the solution, which maybe is not the right one but at least it works.
.sheet(isPresented: $showAddModal) {
AddCategoryView().environment(\.managedObjectContext, self.managedObjectContext)
}
Yep, just passing the Environment with the context to the Modal View fixed the issue. I am not sure this is the correct way to do it.