How to fill a list using Completion Handlers [SWIFT UI] - swiftui

I want to fill a list using a completion handler, the problem is that it's loading nil in the first execution and marks errors when I try to consume my View where I have my list that it's filled by completion handler... Any suggestions to fix this?
this is how I try to fill my list
let invitationService: InvitationService = InvitationService()
#State private var invitationsList : [Appointment]?
init() {
let defaults = UserDefaults.standard
let userId = defaults.integer(forKey: "userId")
var appointmentList : [Appointment]?
invitationService.getInvitations(id: userId, completionHandler: { (appointment) in
if appointment != nil{
appointmentList = appointment
}
})
_invitationsList = State<[Appointment]?>.init(initialValue: (appointmentList))
}
as you can see I would need to fill my list until InvitationService Request ends but If I try to put it inside the code I got a
Escaping closure captures mutating 'self' parameter
error
I mean
invitationService.getInvitations(id: userId, completionHandler: { (appointment) in
if appointment != nil{
appointmentList = appointment
self._invitationsList = State<[Appointment]?>.init(initialValue: (appointmentList))
}
})
My VIEW
var body: some View {
NavigationView{
ZStack{
colors["LightGray"]?.edgesIgnoringSafeArea(.all)
Text("Hey").onAppear(){
let defaults = UserDefaults.standard
let userId = defaults.integer(forKey: "userId")
self.invitationService.getInvitations(id: userId) { (appointment) in
self.invitationsList = appointment
}
}
List {
ForEach(invitationsList!, id: \.self){invitation in
NavigationLink(destination: InvitationDetailView(invitation: invitation)){
HStack{
VStack(alignment: .center){
Text(invitation.startDate.prefix(2))
.font(.largeTitle)
Text(invitation.startDate[2..<6]).font(.subheadline)
}.padding()
Spacer().frame(width:UIScreen.main.bounds.width/15, alignment: .leading)
ImageView(withURL: invitation.imageProfile,widthValue: 80, heightValue: 80)
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
Spacer()
VStack(alignment: .leading){
Text(invitation.titleVisit)
.font(.headline)
.frame(width: UIScreen.main.bounds.width/3, alignment: .leading)
Text(invitation.typeVisit)
.font(.footnote)
Text(invitation.startDate.suffix(9))
.font(.caption)
}.padding()
}
.background(self.colors["White"])
.cornerRadius(25)
.foregroundColor(self.colors["Black"])
.shadow(radius: 2, x: 0, y: 0)
}
}.onDelete(perform: delete)
}
}.hideNavigationBar(text: "back".localized)
}
}
I'm trying to execute and when I do that invitationsList is nil

I recommend do not put such things into View.init, because SwiftUI view is struct, value, and can be re-created several times during layout/rendering. The better approach is to do this after view appeared (or, what is better, outside view hierarchy at all).
Anyway being in view here is possible approach
...
let invitationService: InvitationService = InvitationService()
#State private var invitationsList : [Appointment]? = nil
// nothing for init in this case
var body: some View {
Text("Any subview here")
.onAppear {
let defaults = UserDefaults.standard
let userId = defaults.integer(forKey: "userId")
self.invitationService.getInvitations(id: userId) { (appointment) in
self.invitationsList = appointment
}
}
}

Related

SwiftUI: Observable Object does not update in View?

I am struggling here for days now: I have a async function that get's called onRecieve from a timer in a LoadingView. It calls the getData function from the class ViewModel. Data gets fetched with an HTTP Get Request and compared: if the fetched ID = to the transactionID in my app and the fetched Status = "Success", then the payment is successful.
This is toggled in my observable class. Have a look:
//View Model
#MainActor class ViewModel: ObservableObject {
#Published var fetchedData = FetchedData()
#Published var successfullPayment: Bool = false
#Published var information: String = "Versuch's weiter!"
// Function to fetch Data from the Databank
func getData() {
guard let url = URL(string: getUrl) else {return}
URLSession.shared.dataTask(with: url) { (data, res, err) in
do{
if let data = data {
let result = try JSONDecoder().decode(FetchedData.self, from: data)
DispatchQueue.main.async {
self.fetchedData = result
if self.fetchedData.id == transactionId && self.fetchedData.statuscode == "Success" {
self.successfullPayment = true
print("Payment was successful")
} else {print("Pending ...")}
}
} else {
print("No data")
}
} catch (let error) {
print(error.localizedDescription)
}
}.resume()
}
}
And this is my observing LoadingView:
struct LoadingView: View {
//Timer
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var counter = 0
#State var paymentCancelled = false
#ObservedObject var observable: ViewModel
var body: some View {
ZStack {
Image("money")
.resizable()
.aspectRatio(contentMode: .fit)
VStack {
if self.observable.successfullPayment == true{
Text("Thanks you" as String)
.font(.largeTitle)
.fontWeight(.black)
.multilineTextAlignment(.center)
.padding(.top, 100)
} else {
Text("Paying ..." as String)
.font(.largeTitle)
.fontWeight(.black)
.multilineTextAlignment(.center)
.padding(.top, 100)
}
PushView(destination: CancelledView(), isActive: $paymentCancelled) {
Spacer()
}
Button {
paymentCancelled.toggle()
print("payment cancelled!")
} label: {
Label("Abbrechen", systemImage: "nosign")
.padding(.horizontal, 40)
.padding(.vertical, 10.0)
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(10)
.font(Font.body.weight(.medium))
}
.padding(.bottom, 50)
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
.onReceive(timer) { time in
if counter == 90 {
timer.upstream.connect().cancel()
print("Timer cancelled")
} else {
ViewModel().getData()
}
counter += 1
}
}
}
But the published var successfullPayment doesn't update the View. What am I missing here? Has it to do with the async function?
I will focus only on the call to getData(). Your view is calling the following command:
ViewModel().getData()
This means that you are calling the function on a new instance of the view model. So, the variables fetchedData and successfullPayment will be updated on an instance which is not the one being used in the view.
The first step would be to use the same instance that you have in your view:
observable.getData()
Be sure that the view calling LoadingView has a #StateObject of type ViewModel and that you are passing it correctly.

SwiftUI navigation decision based on a sheet view presenting 2 choice

I am presenting a "wizard" that will be detecting a BLE device and then if it is the correct one the last view will ask if we want to register or skip.
Edit:{
the view order is: MainView presenting in fullScreenCover a first info view informing on how to detect the BLE device then this one pushes a second view with some info on the nearest BLE device and it is in this view that we have the fork where I am presenting a sheet to ask if the user wants to continue and register the BLE device or skip.
So MAIN > INFOView -> BLE detection (> Register or skip ? RegisterView : Destack to main)
}
I have that last view come up as a sheet it has 2 buttons, the first one as mentioned says "Register" and the other one says "skip". If the user presses the register then we dismiss the sheet and navigate to a view that is gathering personal info to register the BLE device. on the other hand, if the user chooses to skip then the wizard need to de-stack back over to the main view.
Normally in UIKit I would just have a delegate inform me of the choice then if skip was selected. I would call pop to root view controller, otherwise, if the register option was selected I would dismiss the sheet view and then navigate to one more final view and get the user registered.
In SwiftUI I do not know how to deal with that navigation fork. I tried using PassthroughSubject but then I have to set the PassthroughSubject var as a state var and in the end, I just did not get the call back from sending in the selection.
Tried binding then Was hoping to make an onReceive but then it is asking for a publisher and that felt wrong to create a publisher just for that.
I am wondering g what is the best way do take care of this in. swiftUI ?
edit:
this is the code (updated with the replay from #Predrag Samardzic) for the view that shows the info on the BLE device (smart bike) and will push at first a request to know if the user wants to register or not, then if yes push that registration screen if not dismiss the entire stack.
struct A18BikeDiscoveryView: View {
#EnvironmentObject var bleManager: ArgonBLEManager
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
private let shouldShowRegistration = CurrentValueSubject<Bool, Never>(false)
#State var isSheetPresented = false
#State var isRegistrationPresented = false
var body: some View {
VStack{
NavigationLink(
destination: A18RegistrationQuestionairy(QuestionairyViewModel()),
isActive: $isRegistrationPresented
) {
EmptyView()
}
A18ImageTextBanner(text: NSLocalizedString("bike_discovery_view_title", comment: ""))
.padding(.bottom, 35)
.navigationBarBackButtonHidden(true)
if let value = bleManager.model?.bikeInfo?.bikeModel{
Text(value)
.fontWeight(.bold)
.scaledFont(.largeTitle)
}
Image("subitoBike")
.resizable()
.frame(minWidth: 0334, idealWidth: 334, maxWidth: .infinity, minHeight: 223, idealHeight: 223, maxHeight: .infinity, alignment: .center)
.aspectRatio(contentMode: .fit)
.padding(.bottom, 10)
Divider()
VStack(alignment: .leading){
HStack{
Text("bike_discovery_view_year_created")
if let v = bleManager.model?.bikeInfo?.year{
Text(v)
}
}
HStack{
Text("bike_discovery_view_model_size")
Text("\(getSizeFromSerial())")
}
HStack{
Text("bike_discovery_view_bike_serial_number")
if let v = bleManager.model?.bikeInfo?.bikeSerialNumber {
Text(v)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 66, alignment: .leading)
.padding(.horizontal, 40)
Divider()
.padding(.bottom, 30)
Button(action: {
isSheetPresented = true
}, label: {
Text("bike_discovery_view_bike_pairing_button_title")
.fontWeight(.bold)
.foregroundColor(.white)
})
.buttonStyle(A18RoundButtonStyle(bgColor: .red))
.padding(.horizontal)
.sheet(
isPresented: $isSheetPresented,
onDismiss: {
if shouldShowRegistration.value {
isRegistrationPresented = true
}},
content: {
A18BikeParingSelection(shouldShowRegistration: shouldShowRegistration)
})
.onReceive(shouldShowRegistration) { shouldShowRegistration in
isSheetPresented = false
}
Button(action: {
bleManager.disconect()
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("bike_discovery_view_bike_pairing_cancel_button_title")
.fontWeight(.bold)
.foregroundColor(Color("grey55"))
})
.padding()
Spacer()
}
.navigationBarColor(backgroundColor: .white, tintColor: .black)
.navigationBarTitleDisplayMode(.inline)
}
func getSizeFromSerial() -> String {
if let serial = bleManager.model?.bikeInfo?.bikeSerialNumber {
if serial.contains("XXS"){
return "XXS"
}else if serial.contains("XSM") {
return "XS"
}else if serial.contains("SML"){
return "S"
}else if serial.contains("MED"){
return "M"
}else if serial.contains("LAR"){
return "L"
}
}
return "N/A"
}
}
This is one possible solution - using CurrentValueSubject in order to trigger dismiss and keep info about the choice made on the presented screen. Then, if registration is needed, you trigger it when sheet is dismissed.
struct MainView: View {
private let shouldShowRegistration = CurrentValueSubject<Bool, Never>(false)
#State var isSheetPresented = false
#State var isRegistrationPresented = false
var body: some View {
VStack {
// this part is if you want to push registration screen, you will need to have MainView inside NavigationView for it
NavigationLink(
destination: RegistrationView(),
isActive: $isRegistrationPresented
) {
EmptyView()
}
// ----------------------------------------------------
Button {
isSheetPresented = true
} label: {
Text("Present sheet")
}
.sheet(
isPresented: $isSheetPresented,
onDismiss: {
if shouldShowRegistration.value {
isRegistrationPresented = true
}},
content: {
ChoiceView(shouldShowRegistration: shouldShowRegistration)
})
.onReceive(shouldShowRegistration) { shouldShowRegistration in
isSheetPresented = false
}
// this part is if you want to present registration screen as sheet
// .sheet(
// isPresented: $isRegistrationPresented,
// content: {
// RegistrationView()
// })
}
}
}
struct ChoiceView: View {
let shouldShowRegistration: CurrentValueSubject<Bool, Never>
var body: some View {
VStack{
Button {
shouldShowRegistration.send(false)
} label: {
Text("Dismiss")
}
Button {
shouldShowRegistration.send(true)
} label: {
Text("Register")
}
}
}
}
struct RegistrationView: View {
var body: some View {
Text("Registration")
}
}

Display filename in next View too

I have a code that makes a http Request, gets an array with filenames from that, displays them each with an image and the filename below. Everything works fine.
Now I made each image a button that opens a detail page.
That works but at the top it should say the matching filename from the page before.
But I am not able to hand over the filename (name) from ContentView4 to the next page (ts).
The language is SwiftUi
Could you please help me?
Thanks
Nikias
Here is my code:
import SwiftUI
struct ContentView4: View {
#State var showingDetail = false
#State var username: String = "."
#State var password: String = "."
#State private var name = String("Nikias2")
#State private var t = String()
#State private var x = -1
#State var dateien = ["word.png"]
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
$showingDetail) {
ts(name: t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
doHttpRequest()
}
}
}
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
ContentView4()
}
}
struct ts: View {
#State var hin = false
#State var um = false
#State var datname: String = ""
var name: String
var body: some View {
NavigationView {
VStack {
Text(name)
.font(.system(size: 60))
.foregroundColor(.black)
.padding(50)
Button(action: {
self.hin.toggle()
}) {
Text("+")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.yellow)
.cornerRadius(35.0)
}
.padding()
if hin {
HStack {
Text("Datei auswählen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei hochladen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei herunterladen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.blue)
.cornerRadius(35.0)
Button(action: {
self.um.toggle()
}) {
Text("Datei umbenennen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.green)
.cornerRadius(35.0)
}
.padding()
if um {
HStack {
TextField(name, text: $datname)
.font(.headline)
.frame(width: 150, height: 70)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei umbenennen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.green)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei löschen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.red)
.cornerRadius(35.0)
}
}
}
}
I believe your issue is a result of using #State variables to store all of the attributes. #State variables are not consistent and get refreshed in the background by SwiftUI depending on your views visibility.
The piece that you are missing is a view controller class stored in an #EnviornmentObject variable. This class gets Initiated in your main contentView and is used to keep track and alter of all your attributes.
Each ContentView should reference the single #EnviornmentObject and pull data from that class.
Another solution which may work would be to replace all your #State variables with #StateObject vars. #StateObject vars are basically #State vars but get initiated before the struct get loaded and the value is kept consistent regardless of the view state of the parent struct.
Here is a rough implementation of #EnvironmentObject within your project.
Basically use the #EnvironmentObject to pass values to child views
ContentView4.swift
struct ContentView4: View {
#EnvironmentObject cv4Controller: ContentView4Controller
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< cv4Controller.dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = cv4Controller.dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(cv4Controller.t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
cv4Controller.$showingDetail) {
ts(name: cv4Controller.t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
cv4Controller.doHttpRequest()
}
}
}
ContentView4Controller.swift
class ContentView4Controller: ObservableObject {
#Published var showingDetail = false
#Published var username: String = "."
#Published var password: String = "."
#Published private var name = String("Nikias2")
#Published private var t = String()
#Published private var x = -1
#Published private var t = String()
#Published private var x = -1
#Published var dateien = ["word.png"]
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
Example of main ContentView.swift
struct ContentView: View {
var cv4Controller: ContentView4Controller = ContentView4Controller()
var body: some view {
// your main page output
GeometryReader { geo in
// just a guess for what you have in your main contentView
switch(page) {
case .main:
ContentView2()
default:
ContentView4()
break
}
}.environmentObject(cv4Controller) // this will make cv4Controller available to all child view structs
}
}
Add #Binding wrapper to the "name" variable in your ts view. And pass the t variable as a binding by adding a "$". This will keep your ts name variable updated to whatever is value it has in the parent view.
Also why do you use a NavigationView in your ts View?
struct ContentView4: View {
...
#State private var t = String()
...
var body: some View {
...
ZStack{
...
}
.sheet(isPresented: $showingDetail) {
ts(name: $t)
}
...
}
func doHttpRequest() {
...
}
}
struct ts: View {
...
#Binding var name: String
var body: some View {
...
}
}
My starting code works, but It's just displaying the Filenames in a row and if I tap a random image, the name won't fit, only if I'm going down in the row and tap them. The problem is, that I don't know how to set the variable to the id, not to pass them to the next view. Has anyone got and idea how I can pass the right filename into a variable in the for loop and read it in the next view?

How to save that my button has already been tapped in SwiftUI

I have the following code that, when tapped it will add the number of likes to a post, it will only allow the user to tap it once, works well but when I reload the app I can like it again, been trying to workout the best way to save that it has been tapped already. I have added the button state as false:
#State var buttonTapped = false
Button(action:
{
self.buttonTapped.toggle() //only allow one tap
let like = Int.init(post.likes)!
ref.collection("Posts").document(post.id).updateData(["likes": "\(like
+ 1)"]) { (err) in
if err != nil{
print((err!.localizedDescription))
return
}
// postData.getAllPosts()
print("updated...")
}
}
) {
Image(systemName: "flame")
.resizable()
.frame(width: 20, height: 20)
}.disabled(buttonTapped)
Any pointers in the right direction would be greatly appreciated
You can use UserDefaults to store the value
struct ContentView : View {
#State var buttonTapped : Bool = UserDefaults.standard.bool(forKey: "buttonTapped")
var body : some View {
Button(action: {
UserDefaults.standard.set(true, forKey: "buttonTapped")
buttonTapped.toggle()
}) {
Image(systemName: "flame")
.resizable()
.frame(width: 20, height: 20)
}.disabled(buttonTapped)
}
}

SwiftUI - Custom Navigation "Back" button, oddly jumping back and forth between views

Here's the situation, I have a Master / Detail view set up. When navigating from the "Events" view to the Events Details view. If a user taps the "Back" button, which I have designed using "Button(action: {self.presentationMode.wrappedValue.dismiss()})..", the view will temporarily change back to the Events list, but then jumps automatically back to the details view that a user was navigating from.
Here's the code on the Events List page
import SwiftUI
import Firebase
struct EventsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#State var data: [EventObject] = []
let db = Firestore.firestore()
var body: some View {
ZStack {
VStack {
List {
ForEach((self.data), id: \.self.eventID) { item in
NavigationLink(destination: EventDetail()) {
VStack {
HStack{
Text("\(item.eventDate)")
.font(.footnote)
.foregroundColor(Color("bodyText"))
Spacer()
}
HStack {
Text("\(item.eventTitle)")
.fontWeight(.bold)
.foregroundColor(Color("Charcoal"))
Spacer()
}.padding(.top, 8)
}.padding(.bottom, 16)
} // nav
}
Spacer()
}
.padding(.top, 60)
}
//Floating Navbar
ZStack {
VStack {
GeometryReader { gr in
HStack {
Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
Image(systemName: "chevron.left")
.foregroundColor(Color("Charcoal"))
.padding(.leading, 16)
HStack {
Text("Explore · Disney Events")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(Color("Charcoal"))
.padding()
Spacer()
}
}.frame(width: gr.size.width * 0.92, height: 48)
.background(Color("navBackground"))
.cornerRadius(8)
.shadow(color: Color("Shadow"), radius: 10, x: 2, y: 7)
}.padding(.leading, 16)
Spacer()
}
}
.padding(.top, 50)
.edgesIgnoringSafeArea(.top)
// Floating Nav Ends
}
}.onAppear(perform: self.queryEvents)
}
func queryEvents() {
self.data.removeAll()
self.db.collectionGroup("events").getDocuments() {(querySnapshot, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
for document in querySnapshot!.documents {
let id = document.documentID
let title = document.get("eventTitle") as! String
let shortDesc = document.get("eventShort") as! String
let description = document.get("eventDescription") as! String
let date = document.get("eventDate") as! Timestamp
let aDate = date.dateValue()
let formatter = DateFormatter()
formatter.dateFormat = "E, MMM d · h:mm a"
let formattedTimeZoneStr = formatter.string(from: aDate)
let address = document.get("eventAddress") as! String
let cost = document.get("eventCost") as! Double
let location = document.get("eventLocation") as! String
let webURL = document.get("eventURL") as! String
self.data.append(EventObject(id: id, title: title, shortDesc: shortDesc, description: description, date: formattedTimeZoneStr, address: address, cost: cost, location: location, webURL: webURL))
}
}
}
}
}
class EventObject: ObservableObject {
#Published var eventID: String
#Published var eventTitle: String
#Published var eventShort: String
#Published var eventDescription: String
#Published var eventDate: String
#Published var eventAddress: String
#Published var eventCost: Double
#Published var eventLocation: String
#Published var eventURL: String
init(id: String, title: String, shortDesc: String, description: String, date: String, address: String, cost: Double, location: String, webURL: String) {
eventID = id
eventTitle = title
eventShort = shortDesc
eventDescription = description
eventDate = date
eventAddress = address
eventCost = cost
eventLocation = location
eventURL = webURL
}
}
Event Details stripped down code below. I tried to take things away to search for the cause. It seems to be isolated to the Firebase call.
import SwiftUI
import Firebase
import MapKit
struct EventDetail: View {
#Environment(\.presentationMode) var presentationMode:
Binding<PresentationMode>
// var eventID: String
// var eventTitle: String
// var eventShort: String
// var eventDescription: String
// var eventDate: String
// var eventAddress: String
// var eventCost: Double
// var eventLocation: String
// var eventURL: String
var body: some View {
ZStack {
VStack {
GeometryReader { gr in
HStack {
Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
Image(systemName: "chevron.left")
.foregroundColor(Color("Charcoal"))
.padding(.leading, 16)
HStack {
Text("Events · Event Details")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(Color("Charcoal"))
.padding()
Spacer()
}
}.frame(width: gr.size.width * 0.92, height: 48)
.background(Color("navBackground"))
.cornerRadius(8)
.shadow(color: Color("Shadow"), radius: 10, x: 2, y: 7)
}.padding(.leading, 16)
Spacer()
}
}
.padding(.top, 50)
.edgesIgnoringSafeArea(.top)
}
}
}
Here's a video to illustrate what I'm talking about.
Dropbox Video Link
Here is a demo of possible approach based on simplified variant of your views. The idea is to use tag/selection based NavigationLink constructor and pass binding to selection to EventDetail to deactivate selection via binding and thus activate back navigation.
Note: I think that presentationMode was not designed for navigation scenario.
struct EventsView: View {
#State private var selectedItem: Int? = nil
var body: some View {
NavigationView {
List {
ForEach(0..<10, id: \.self) { item in
NavigationLink("Item \(item)", destination: EventDetail(selected: self.$selectedItem), tag: item, selection: self.$selectedItem)
}
}
}
}
}
struct EventDetail: View {
#Binding var selected: Int?
var body: some View {
VStack {
HStack {
Button(action: { self.selected = nil }) {
Image(systemName: "chevron.left")
HStack {
Text("Events · Event Details")
.padding()
Spacer()
}
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
Spacer()
}
}
}