How we get the data and show like viewdidload using swiftUI - ios

i'm trying to work with swiftUI but i'm having an issue. i want to fetch the data from firebase and show that data to the list. i used mutating function to modify the variable. now when i called that function than it give me error. how i can solve this please help me. below are the code i'm using.
struct UserListView : View {
var body: some View{
VStack{
List{
VStack(alignment:.leading){
HStack{
Text("Favroute").frame(alignment: .leading).padding(.leading, 20)
Spacer()
Text("All").frame(alignment: .trailing)
}
ScrollView(.horizontal, content: {
HStack(spacing: 10) {
ForEach(0..<10) { index in
StatusView(statusImage: "nature")
}
}
.padding(.leading, 10)
})
.frame(height: 190)
}
ForEach(0..<10){_ in
HStack{
Group{
NavigationLink(destination: ChatView()){
UserImageView(imageName: "profileDummy").padding(.leading, 5)
Text("User Name").padding(.leading,10)
}
}
}.frame( height: 40)
}.environment(\.defaultMinListRowHeight, 40)
}
}
.navigationBarTitle("UserList",displayMode: .inline)
.onAppear {
self.userList()
}
}
var userDataList = [UserModel]()
mutating func userList() {
databaseReference.child(DatabaseNode.Users.root.rawValue).observe(.value) { (snapShot) in
if snapShot.exists(){
if let friendsDictionary = snapShot.value{
for each in friendsDictionary as! [String : AnyObject]{
print(each)
if let user = each.value as? [String : Any]{
let data = UserModel(userId: JSON(user["userId"] ?? "").stringValue, name: JSON(user["name"] ?? "").stringValue)
print(data)
self.userDataList.append(data)
}
}
}
}
}
}
}
below are the error message i'm getting:
Cannot use mutating member on immutable value: 'self' is immutable

You should declare your userDataList property like so #State var userDataList = [UserModel](). This way this property will be stored by SwiftUI outside of your struct and can be mutated.

Related

Error: unrecognized selector sent to instance 0x6000027f1ea0' & Process variables after receive parameters and before render view

I have two views here: RoomDetailView and FeaturedTabView
In the FeaturedTabView, there are two variables imageString and roomImages, the imageString will receive data from RoomDetailView as parameter and RoomImages is for storing an array which is split from imageString after the imageString is assigned with the string coming from RoomDetailView.
I have two questions here:
My app keep crashing and return error: terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Room roomImages]: unrecognized selector sent to instance 0x6000027f1ea0'. However, I am pretty sure that my variable type is correct and is aligned with my coreData attribute type.
May I ask is there anywhere I can first process the string, split to array ( try using init but I dont think that is the correct way to go ) and assign to another roomImages variable before the view get rendered.
struct RoomDetailView: View {
let room: Room;
var body: some View {
ZStack{
VStack(spacing: 0){
FeaturedTabView(imageString: room.roomImages ?? "room-1,")
.padding(.vertical, 20)
})
}
.background(Color.white.ignoresSafeArea(.all, edges: .all))
}
.ignoresSafeArea(.all, edges: .top)
}
}
struct FeaturedTabView: View {
let imageString: String
let roomImages: [String]
var body: some View {
TabView{
ForEach(imageString, id: \.self){
img in FeaturedItemView(img: img)
.padding(.top, 10)
.padding(.horizontal, 15)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
init(imageString: String){
self.imageString = imageString
self.roomImages = self.imageString.components(separatedBy: ",")
}
}
Room Entity
Try this example code, using roomImages in the ForEach loop.
// for testing
struct ContentView: View {
var body: some View {
RoomDetailView(room: Room())
}
}
// for testing
struct Room: Identifiable {
let id = UUID()
var roomImages: String? = "room-1, room-2" // <-- for testing
}
struct RoomDetailView: View {
let room: Room
var body: some View {
ZStack{
VStack(spacing: 0) {
FeaturedTabView(imageString: room.roomImages ?? "room-1,")
.padding(.vertical, 20)
}
}
.background(Color.white.ignoresSafeArea(.all, edges: .all))
.ignoresSafeArea(.all, edges: .top)
}
}
struct FeaturedTabView: View {
let imageString: String
let roomImages: [String]
var body: some View {
TabView {
ForEach(roomImages, id: \.self){ img in // <-- here
FeaturedItemView(img: img)
.padding(.top, 10)
.padding(.horizontal, 15)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
init(imageString: String) {
self.imageString = imageString
self.roomImages = self.imageString.components(separatedBy: ",")
}
}
// for testing
struct FeaturedItemView: View {
let img: String
var body: some View {
Text(img)
}
}

SwiftUI code too long for the compiler to type-check it, but I can't seem to break it down into parts

I am trying to make a view iteratively from a JSON file fetched from a PHP URL. The view is made with a for each loop to create an information box for every different station in the JSON and also makes them a navigation link. Ideally, I would tap the box to get more information on a specific tide station (all station data hasn't been presented on the view yet).
Since I am using a for each loop, I represent the current station number in the data array with i. In order to get the same data as the box I click I use the same i number for other relevant data.
My issue is that as I am building everything in the same for each loop to keep that number i in the scope, my code gets too long for the compiler to check. I heard that this could happen even if the code wasn't too long but simply for a typing mistake somewhere, but I have yet to find a mistake that breaks the code and truly believe it is due to the length as it was working if I commented-out some parts.
To resolve that I understand I need to break my code into different sections for the compiler to check them individually so as not to start the process every time.
However, as I am building everything into the for each loop to have the i number in the scope, I cannot make sub-views that use that number.
I am not entirely sure this is the question that would best solve my issue, but how can I pass the for each loop parameter into another view/function/or something else?
I apologize for the code being very rough and not following good coding practices, if not just for SwiftUI, I am quite inexperienced with programming in general. I left the entirety of the code to be sure not to leave any possibility out.
import Foundation
import SwiftUI
import Alamofire
import SwiftyJSON
import MapKit
public var array_tides_site_name = [String]()
public var array_tides_next_lw_time = [String]()
public var array_tides_next_lw_height = [Double]()
public var array_tides_next_hw_time = [String]()
public var array_tides_next_hw_height = [Double]()
public var array_tides_tidal_state = [String]()
public var array_tides_latitude = [Double]()
public var array_tides_longitude = [Double]()
public var array_tides_observed_height = [Double]()
public var array_tides_predicted_height = [Double]()
public var array_tides_surge = [Double]()
struct Previews_GeneralTides_Previews: PreviewProvider {
static var previews: some View {
GeneralTidesView()
}
}
struct GeneralTidesView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack (alignment: .center) {
Spacer()
Image(systemName: "chart.xyaxis.line")
.font(.largeTitle)
.frame(width: 300, height: 300, alignment: .center)
Spacer()
Text("More Stations")
.font(.title3)
.foregroundStyle(LinearGradient(colors: [.primary, .secondary], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: screenWidth, height: 40)
.background(.thickMaterial)
Spacer()
Divider()
.onAppear() {loadStationData()}
Spacer()
StationList()
}
.navigationBarTitle("Tides")
.navigationBarTitleDisplayMode(.inline)
}
}
}
func loadStationData(){
let generalTideUrl = "http://www.pla.co.uk/hydrographics/ajax/ltoverview.php"
AF.request(generalTideUrl, method: .get).responseJSON(){ (generalTideResponse) in
switch generalTideResponse.result {
case .success:
//print(generaltideresponse.result)
let generalTideResult = try? JSON(data: generalTideResponse.data!)
//print(generaltideresult)
//print(generalTideResult!["tides"])
let generalTideArray = generalTideResult!["tides"]
array_tides_site_name.removeAll()
array_tides_next_lw_time.removeAll()
array_tides_next_lw_height.removeAll()
array_tides_next_hw_time.removeAll()
array_tides_next_hw_height.removeAll()
array_tides_tidal_state.removeAll()
array_tides_latitude.removeAll()
array_tides_longitude.removeAll()
array_tides_observed_height.removeAll()
array_tides_predicted_height.removeAll()
array_tides_surge.removeAll()
for i in generalTideArray.arrayValue {
//print(i)
let site_name = i["site_name"].stringValue
array_tides_site_name.append(site_name)
var next_lw_time = i["next_lw_time"].stringValue
let lwRange = next_lw_time.startIndex..<next_lw_time.index(next_lw_time.startIndex, offsetBy: 11)
next_lw_time.removeSubrange(lwRange)
array_tides_next_lw_time.append(next_lw_time)
let next_lw_height = i["next_lw_height"].doubleValue
array_tides_next_lw_height.append(next_lw_height)
var next_hw_time = i["next_hw_time"].stringValue
let hwRange = next_hw_time.startIndex..<next_hw_time.index(next_hw_time.startIndex, offsetBy: 11)
next_hw_time.removeSubrange(hwRange)
array_tides_next_hw_time.append(next_hw_time)
let next_hw_height = i["next_hw_height"].doubleValue
array_tides_next_hw_height.append(next_hw_height)
let tidal_state = i["tidal_state"].stringValue
array_tides_tidal_state.append(tidal_state)
let latitude = i["latitude"].doubleValue
array_tides_latitude.append(latitude)
let longitude = i["longitude"].doubleValue
array_tides_longitude.append(longitude)
let predictedHeight = i["predicted_height"].doubleValue
array_tides_predicted_height.append(predictedHeight)
let observedHeight = i["observed_height"].doubleValue
array_tides_observed_height.append(observedHeight)
let surge = i["surge_height"].doubleValue
array_tides_surge.append(surge)
}
break
case .failure:
print(generalTideResponse.error!)
break
}
}.resume()
}
struct StationList: View {
#State private var mapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.5, longitude: -0.12), span: MKCoordinateSpan(latitudeDelta: 0.6, longitudeDelta: 0.6))
var body: some View {
ForEach(0..<array_tides_site_name.count) { i in
NavigationLink(destination: Group{
VStack(alignment: .center) {
stationNavLink()
Text(array_tides_site_name[i])
.font(.largeTitle)
Map(coordinateRegion: $mapRegion, annotationItems: [Location(name: array_tides_site_name[i], coordinate: CLLocationCoordinate2D(latitude: array_tides_latitude[i], longitude: array_tides_longitude[i]))]) { location in
MapMarker(coordinate: location.coordinate)}
.frame(width: screenWidth, height: 250)
HStack {
RoundedRectangle(cornerRadius: 10)
.foregroundStyle(.thinMaterial)
.frame(width: 150, height: 120)
.overlay {
VStack(alignment: .leading , spacing: 10) {
Text("Next Low Tide:")
HStack {Text("Time: "); Text(array_tides_next_lw_time[i])}.foregroundColor(.secondary)
HStack {Text("Height: "); Text(array_tides_next_lw_height[i].description); Text("m")}.foregroundColor(.secondary)
}
}
RoundedRectangle(cornerRadius: 10)
.foregroundStyle(.thinMaterial)
.frame(width: 150, height: 120)
.overlay {
VStack(alignment: .leading, spacing: 10) {
Text("Next High Tide:")
HStack {Text("Time: "); Text(array_tides_next_hw_time[i])}.foregroundColor(.secondary)
HStack {Text("Height: "); Text(array_tides_next_hw_height[i].description); Text("m")}.foregroundColor(.secondary)
}
}
}
Text(array_tides_surge[i].description)
}
}){
ZStack {
RoundedRectangle(cornerRadius: 8)
.strokeBorder(.white.opacity(0.3), lineWidth: 1)
.background(RoundedRectangle(cornerRadius: 8).fill(.thinMaterial))
.frame(width: screenWidth - 40, height: 80)
.overlay() {
VStack(alignment: .leading) {
Spacer()
Text(array_tides_site_name[i])
.padding(.leading, 10)
.foregroundStyle(LinearGradient(colors: [.secondary, .secondary.opacity(0.8)], startPoint: .leading, endPoint: .trailing))
Spacer()
Divider()
Spacer()
Group {
HStack(){
Spacer()
Text("High Water: ")
Text(array_tides_next_hw_time[i])
Spacer()
Text("Low Water: ")
Text(array_tides_next_lw_time[i])
Spacer()
if array_tides_tidal_state[i] == "Flood" { Image(systemName: "arrow.up").foregroundColor(.green) }
else { Image(systemName: "arrow.down").foregroundColor(.red) }
Spacer()
}
}
Spacer()
}
}
}
}
}
}
}
var stationNavLink: some View {
Text(array_tides_surge[currentStation])
}
struct Location: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D
}
Having multiple, associated, arrays to hold your data is a definite code smell. You risk having data get out of sync between the arrays and as you can see, loading and accessing data involves a lot of code.
You can start by creating some [Codable] structs to hold you data. quicktype.io can do this for. Simply paste in your JSON and you get the structs you need.
I have modified Tide to conform to Hashable and Identifiable to make it easier to use in SwiftUI lists.
Also note the use of CodingKeys to convert the properties to Swift naming conventions.
// MARK: - TideData
struct TideData: Codable {
let timezone: String
let tides: [Tide]
}
// MARK: - Tide
struct Tide: Codable, Hashable, Identifiable {
let siteID, siteStation: Int
let siteName: String
let latitude, longitude: Double
let received: String
let observedHeight, predictedHeight, surgeHeight: Double
let nextLwTime: String
let nextLwHeight: Double
let nextHwTime: String
let nextHwHeight: Double
let tidalState: String
var id: Int {
return siteID
}
enum CodingKeys: String, CodingKey {
case siteID = "site_id"
case siteStation = "site_station"
case siteName = "site_name"
case latitude, longitude
case received = "Received"
case observedHeight = "observed_height"
case predictedHeight = "predicted_height"
case surgeHeight = "surge_height"
case nextLwTime = "next_lw_time"
case nextLwHeight = "next_lw_height"
case nextHwTime = "next_hw_time"
case nextHwHeight = "next_hw_height"
case tidalState = "tidal_state"
}
}
Now you can move your loading code into its own class. Make this an ObservableObject and have it publish the tide data.
Since our structs conform to Codable we can use AlamoFire's inbuilt JSON decoding and transformation
lass Loader:ObservableObject {
#Published var tides: [Tide] = []
func load() {
let generalTideUrl = "https://www.pla.co.uk/hydrographics/ajax/ltoverview.php"
AF.request(generalTideUrl, method: .get).responseDecodable(of: TideData.self){ (generalTideResponse) in
switch generalTideResponse.result {
case .success(let tideData):
self.tides = tideData.tides
print(self.tides)
case .failure(let error):
print(error)
}
}
}
}
Much more succinct!
Finally, we can use this data in a SwiftUI view
I have simplified your views for the sake of this answer, but I am sure you will get the idea.
struct GeneralTidesView: View {
#ObservedObject var loader: Loader
var body: some View {
NavigationView {
List {
ForEach(loader.tides) { station in
NavigationLink(destination: StationView(station:station)) {
Text(station.siteName)
}
}
}
}.onAppear {
self.loader.load()
}
}
}
struct Previews_GeneralTides_Previews: PreviewProvider {
static var previews: some View {
GeneralTidesView(loader: Loader())
}
}
struct StationView: View {
var station: Tide
var body: some View {
Form {
HStack {
Text("Predicted Height")
Text("\(station.observedHeight)")
}
HStack {
Text("Predicted Height")
Text("\(station.predictedHeight)")
}
}.navigationTitle(station.siteName)
}
}
You can see how having a struct with properties makes it much easier to pass data around. Also, since all of the properties are in a single struct, we no longer need to work about array indices.

SwiftUI Change icon by number

I want the Images to change according to the text, but it works over .degisim String and int value in the ready-made code block I found. That's why it doesn't work for String.
import SwiftUI
struct altin: View {
#State var users: [Altin] = []
var body: some View {
ZStack{
List(users) { altin in
NavigationLink {
detayView(altinKur: altin)
} label: {
HStack{
Image(systemName: getImage(percent: Int(altin.degisim)))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 9, height: 9)
.foregroundColor(
(altin.degisim).contains("-") ?
Color.down :
Color.up)
.padding(.trailing, 5)
Text(altin.degisim)
.font(.system(size: 16))
.fontWeight(.medium)
.foregroundColor(
(altin.degisim).contains("-") ?
Color.down :
Color.up)
}
}
}
}
.listStyle(PlainListStyle())
.padding(0)
.onAppear {
apiCall().getUsers { (users) in
self.users = users
}
}
}
}
struct altin_Previews: PreviewProvider {
static var previews: some View {
altin()
}
}
func getImage(percent: Int) -> String{
if percent > 0 {
return "chevron.up"
}else{
return "chevron.down"
}
}
.foregroundcolor works but not within Image.
The code is more complex, I simplified it for solution.
Model:
struct Altin: Codable, Identifiable {
let id = UUID()
let name: String
let alis: String
let satis: String
let degisim: String
}

How do I let text retrieved from Firestore load upon the view being shown?

Intro
Hi there. I recently asked a different question asking how to implement user-triggered FCMs. I quickly realised that in order to implement FCMs I needed to add another feature to my app, which is why I am here now. I'm trying, trust me.
My question / problem
In the picture I attached, you can see the text that is supposed to show when the user clicks on the friends tab. But it doesn't do that. It does when I refresh the list, because I put the function in the refreshable attribute. I did also put the function in the view initialisation, but it doesn't do what I want it to do. So my question would be, a) why it doesn't load? and b) what would be an approach to solve my problem?
Code reference
This function is called in the init{} of the view and .refreshable{} of the list inside the view. (I also tried adding it via .onAppear{} in the NavigationLink of the parent view.)
#State var bestfriend: String = ""
func getBestie() {
let db = Firestore.firestore()
let docRef = db.collection("users").document(email)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let bestie = document.get("bestie") as? String ?? "error: bestie"
bestfriend = String(bestie)
} else {
print("Document does not exist")
}
}
}
Image for reference
Thanks and annotation
Thank you very much in advance, I'm amazed every day by how amazing this community is and how so many people are willing to help. If you need me to add anything else, of course I will do that. I hope I'll be wise enough one day to help other people with their problems as well.
Edit
The view
import Firebase
import FirebaseAuth
import SDWebImage
import SDWebImageSwiftUI
import SwiftUI
struct View_Friend_Tab: View {
#ObservedObject var friends_model = Model_User()
#State var friendWho = ""
init() {
friends_model.getFriendlist()
//friends_model.getBestie()
getBestie()
}
//VARS
let gifurl = URL(string: "https://c.tenor.com/BTCEb08QgBgAAAAC/osita-iheme-aki-and-pawpaw.gif")
let avatarURL = URL(
string:
"https://firebasestorage.googleapis.com/v0/b/universerp-72af2.appspot.com/o/avatars%2Fanime-girl-white-hair-1-cropped.jpg?alt=media&token=efba4215-850d-41c8-8c90-385f7a572e94"
)
#State var showingAlert = false
#State var showFriendRequests = false
#State var bestfriend: String = ""
func getBestie() {
let db = Firestore.firestore()
let docRef = db.collection("users").document(email)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let bestie = document.get("bestie") as? String ?? "error: bestie"
bestfriend = String(bestie)
} else {
print("Document does not exist")
}
}
}
var body: some View {
if !showFriendRequests {
VStack {
NavigationView {
/*
List (friends_model.friend_list) { item in
HStack {
Text(item.email)
Spacer()
}
}
.refreshable{
friends_model.getFriendlist()
}
.listStyle(.grouped)
*/
List {
Section(header: Text("management")) {
NavigationLink(destination: View_Friend_Best_Tab()) {
Label("Select your bestie", systemImage: "star.fill")
}
NavigationLink(destination: View_Friend_Requests_Tab()) {
Label("Manage friend requests", systemImage: "person.fill.questionmark")
}
NavigationLink(destination: View_Friend_Add_Tab()) {
Label("Add friend", systemImage: "person.fill.badge.plus")
}
}
ForEach(friends_model.friend_list) { item in
let avURL = URL(string: item.avatarURL)
Section(header: Text(item.username)) {
HStack {
VStack(alignment: .leading) {
if bestfriend == item.email {
Text("Is your Shin'yū")
.foregroundColor(Color("lightRed"))
.fontWeight(.bold)
.font(.footnote)
}
Text(item.username)
.fontWeight(.bold)
.frame(alignment: .leading)
Text(item.email)
.font(.footnote)
.multilineTextAlignment(.leading)
}
Spacer()
WebImage(url: avURL)
.resizable(resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.clipShape(Circle())
.shadow(radius: 5)
.overlay(Circle().stroke(Color.black, lineWidth: 1))
}
Button("Remove", role: .destructive) {
showingAlert = true
}
.alert("Do you really want to remove this friend?", isPresented: $showingAlert) {
HStack {
Button("Cancel", role: .cancel) {}
Button("Remove", role: .destructive) {
friendWho = item.email
removeFriend()
withAnimation {
friends_model.getFriendlist()
}
}
}
}
}
}
}
.navigationTitle("Your Friends")
.navigationViewStyle(.automatic)
.refreshable {
friends_model.getFriendlist()
getBestie()
}
.listStyle(.insetGrouped)
Spacer()
}
}
} else {
View_Friend_Requests_Tab()
}
}
}
struct View_Friend_Tab_Previews: PreviewProvider {
static var previews: some View {
View_Friend_Tab()
}
}
As previously stated, the function is being called in the init block and when the list is refreshed.
As a general recommendation, use view models to keep your view code clean.
In SwiftUI, a view's initialiser should not perform any expensive / long-running computations. Keep in mind that in SwiftUI, a view is only a description of your UI, not the UI itself. Any state management should be handled outside of the initialiser.
In your case, use .onAppear or .task:
import Firebase
import FirebaseAuth
import SDWebImage
import SDWebImageSwiftUI
import SwiftUI
class FriendsViewModel: ObservableObject {
#Published var bestfriend: String = ""
func getBestie() {
let db = Firestore.firestore()
let docRef = db.collection("users").document(email)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
// consider using Codable for mapping - see https://peterfriese.dev/posts/firestore-codable-the-comprehensive-guide/
let bestie = document.get("bestie") as? String ?? "error: bestie"
self.bestfriend = String(bestie)
} else {
print("Document does not exist")
}
}
}
// add other functions and properties here.
}
struct FriendsView: View {
#ObservedObject var viewModel = FriendsViewModel()
//VARS
let gifurl = URL(string: "https://c.tenor.com/BTCEb08QgBgAAAAC/osita-iheme-aki-and-pawpaw.gif")
let avatarURL = URL(
string:
"https://firebasestorage.googleapis.com/v0/b/universerp-72af2.appspot.com/o/avatars%2Fanime-girl-white-hair-1-cropped.jpg?alt=media&token=efba4215-850d-41c8-8c90-385f7a572e94"
)
#State var showingAlert = false
#State var showFriendRequests = false
#State var bestfriend: String = ""
var body: some View {
if !showFriendRequests {
VStack {
NavigationView {
List {
Section(header: Text("management")) {
NavigationLink(destination: SelectBestieView()) {
Label("Select your bestie", systemImage: "star.fill")
}
NavigationLink(destination: ManageFriendRequestsView()) {
Label("Manage friend requests", systemImage: "person.fill.questionmark")
}
NavigationLink(destination: AddFriendsView()) {
Label("Add friend", systemImage: "person.fill.badge.plus")
}
}
ForEach(viewModel.friends) { friend in
let avURL = URL(string: friend.avatarURL)
Section(header: Text(friend.username)) {
HStack {
VStack(alignment: .leading) {
if bestfriend == friend.email {
Text("Is your Shin'yū")
.foregroundColor(Color("lightRed"))
.fontWeight(.bold)
.font(.footnote)
}
Text(friend.username)
.fontWeight(.bold)
.frame(alignment: .leading)
Text(friend.email)
.font(.footnote)
.multilineTextAlignment(.leading)
}
Spacer()
WebImage(url: avURL)
.resizable(resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.clipShape(Circle())
.shadow(radius: 5)
.overlay(Circle().stroke(Color.black, lineWidth: 1))
}
Button("Remove", role: .destructive) {
showingAlert = true
}
.alert("Do you really want to remove this friend?", isPresented: $showingAlert) {
HStack {
Button("Cancel", role: .cancel) {}
Button("Remove", role: .destructive) {
friendWho = friend.email
viewModel.removeFriend()
withAnimation {
viewModel.getFriendlist()
}
}
}
}
}
}
}
.navigationTitle("Your Friends")
.navigationViewStyle(.automatic)
.onAppear {
viewModel.getFriendlist()
viewModelgetBestie()
}
.refreshable {
viewModel.getFriendlist()
viewModelgetBestie()
}
.listStyle(.insetGrouped)
Spacer()
}
}
} else {
FriendRequestsView()
}
}
}
struct FriendsViewPreviews: PreviewProvider {
static var previews: some View {
FriendsView()
}
}

Update view bugs when data in firebase changes

This is when I launch the app and it looks okThis is when I change some data from firebase, as you can see some data is not arranged in the right way and some rectangles are doubleI'm currently working on this project where I need do read data from Firebase and update my views from the changes. Every time my data changes on firebase my views bugs in strange ways.
This is my Student class
struct Student : Identifiable {
var id : String
var name : String
var surname : String
var isAbsent : Bool
var isBathroom : Bool
}
Here where I get my data from firebase and publish to get all other views the data
class getStudents: ObservableObject {
#Published var datas = [Student]()
init() {
let db = Firestore.firestore()
let classRef = db.collection("5SA")
classRef.order(by: "surname").addSnapshotListener {(snap,error) in
if error != nil{
print((error?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
let id = i.document.documentID
let name = i.document.get("name")as! String
let surname = i.document.get("surname") as! String
let isAbsent = i.document.get("absent") as! Bool
let isBathroom = i.document.get("bathroom") as! Bool
self.datas.append(Student(id: id, name: name, surname: surname, isAbsent: isAbsent, isBathroom: isBathroom))
if i.type == .modified{
let isAbsent = i.document.get("absent") as! Bool
let isBathroom = i.document.get("bathroom") as! Bool
for j in 0..<self.datas.count{
if self.datas[j].id == id{
self.datas[j].isAbsent = isAbsent
self.datas[j].isBathroom = isBathroom
}
}
}
}
}
}
}
This are my views. Apart from the landscape on the iPad simulator the bugs every time.
What I'm trying to achieve is creating like a class app. Where there are students absent, in the toilet or present. I want to to show every student with a rectangle with his name on it and to change color of the rectangles every time the bools on my fire base changes(isAbsent,isBathroom). But when I try to change value on firebase the rectangles multiplies and do strange thinks.
struct ContentView: View {
#ObservedObject var students = getStudents()
var body: some View {
NavigationView{
VStack{
ScrollView{
Text("Menu")
Menu().background(Color.white)
Text("Future verifiche")
HStack{
ScrollView(.horizontal, showsIndicators: false){
RoundedRectangle(cornerRadius: 20).frame(width:300,height: 300).foregroundColor(.white).overlay(Text("V. Matematica"))
}
}
Text("Oggi")
HStack{
Spacer()
Text("Assenti ")
Spacer()
Text("In bagno")
Spacer()
Text("Presenti")
Spacer()
}
HStack{
Spacer()
ScrollView{
ForEach(students.datas,id: \.id){student in
VStack(spacing: 15){
if student.isAbsent{
RoundedRectangle(cornerRadius: 20).frame(width:200,height: 100).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
}
Spacer()
ScrollView{
ForEach(students.datas,id: \.id){student in
VStack(spacing: 15){
if student.isBathroom{
RoundedRectangle(cornerRadius: 20).frame(width:200,height: 100).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
}
Spacer()
ScrollView{
ForEach(students.datas,id: \.id){student in
VStack(spacing: 15){
if !student.isAbsent && !student.isBathroom{
RoundedRectangle(cornerRadius: 20).frame(width:200,height: 100).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
}
Spacer()
}
}
}.navigationBarTitle("Classe 5SA",displayMode: .inline)
}.navigationViewStyle(StackNavigationViewStyle())
}
func getColor(isAbsent: Bool, isBathroom : Bool) -> Color {
if isAbsent{
return Color.red
}
if isBathroom{
return Color.blue
}
return Color.green
}
}
struct Menu : View {
var body : some View{
HStack{
NavigationLink(destination: Studenti()){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.red).opacity(0.5).overlay(Text("Studenti").foregroundColor(.black))
}
NavigationLink(destination: Text("Overview")){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.green).opacity(0.5).overlay(Text("Overview").foregroundColor(.black))
}
NavigationLink(destination: Text("Orario")){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.blue).opacity(0.5).overlay(Text("Orario").foregroundColor(.black))
}
NavigationLink(destination: Text("Calendario")){
RoundedRectangle(cornerRadius: 30).frame(width: 150,height: 150).foregroundColor(.orange).opacity(0.5).overlay(Text("Calendario").foregroundColor(.black))
}
}
}
}
struct Studenti : View {
#ObservedObject var students = getStudents()
var body : some View {
ForEach(students.datas,id: \.id){student in
VStack{
Rectangle().frame(width:300,height: 150).foregroundColor(self.getColor(isAbsent: student.isAbsent, isBathroom: student.isBathroom)).overlay(Text(student.name))
}
}
}
func getColor(isAbsent: Bool, isBathroom : Bool) -> Color {
if isAbsent{
return Color.red
}
if isBathroom{
return Color.blue
}
return Color.green
}
}

Resources