Add a ProgressView during a simple URLSession POST request in SwiftUI - ios

I wanted to show a progress view during a simple URL POST request process. What I would like to do is the button to turn into a ProgressView spinner (usually it's default) as it's going through the requestTest function process. After the request is done, then the progress view goes away and turns back into a button.
here's the code.
struct ContentView: View {
#State private var tweetID = ""
#State private var tweetStatus = ""
#State private var response = ""
#State var showAlert = false
#State var sendToWebhook = false
var body: some View {
NavigationView {
Form {
Section(footer: Text("Test")) {
TextField("Field to place response data", text: $response)
TextEditor( text: $tweetStatus)
.frame(height: 100)
}
Section {
Button("Get Data") {
// Where progress should start before function
ProgressView("Test", value: 100, total: 100)
requestTest() { results in
response = results
if response == "No Data!" {
showAlert = true
}
}
}
if self.requestTest {
ProgressView()
}
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Tweet Sent"), message: Text("Your Tweet is sent! Your Tweet ID is shown in the field"), dismissButton: .default(Text("OK")))
}
}
}
func requestTest(completion: #escaping(String) -> ()) {
if let url = URL(string: "https://requestbin.net/r/ag4ipg7n") {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
components.queryItems = [ URLQueryItem(name: "TweetID", value: response),
URLQueryItem(name: "Status", value: tweetStatus)]
if let query = components.url!.query {
request.httpBody = Data(query.utf8)
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let apiResponse = String(data: data, encoding: .utf8) {
// IF Completed, these actions are shown below
completion(apiResponse)
self.showAlert = true
tweetStatus = ""
} else {
completion("No Data!")
}
}
task.resume()
}
}
}
I thought if I tried to do as a if self.requestTest { ProgressView() }, but no avail as it throwed me a error that says Cannot convert value of type '(#escaping (String) -> ()) -> ()' to expected condition type 'Bool'.
Is there a way to do that?

there are a number of ways to do what you ask. This is
just one approach:
struct ContentView: View {
#State private var tweetID = ""
#State private var tweetStatus = ""
#State private var response = ""
#State var showAlert = false
#State var sendToWebhook = false
#State var inProgress = false // <--- here
var body: some View {
NavigationView {
Form {
Section(footer: Text("Test")) {
TextField("Field to place response data", text: $response)
TextEditor( text: $tweetStatus)
.frame(height: 100)
}
Section {
Button("Get Data") {
inProgress = true // <--- here
requestTest() { results in
inProgress = false // <--- here
response = results
if response == "No Data!" {
showAlert = true
}
}
}
if inProgress { // <--- here
ProgressView()
}
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Tweet Sent"), message: Text("Your Tweet is sent! Your Tweet ID is shown in the field"), dismissButton: .default(Text("OK")))
}
}
}

Related

How to update isLoading state from within URLSession.dataTask() completion handler in Swift?

I created a view (called AddressInputView) in Swift which should do the following:
Get an address from user input
When user hits submit, start ProgressView animation and send the address to backend
Once the call has returned, switch to a ResultView and show results
My problem is that once the user hits submit, then the view switches to the ResultView immediately without waiting for the API call to return. Therefore, the ProgressView animation is only visible for a split second.
This is my code:
AddressInputView
struct AddressInputView: View {
#State var buttonSelected = false
#State var radius = 10_000 // In meters
#State var isLoading = false
#State private var address: String = ""
#State private var results: [Result] = []
func onSubmit() {
if !address.isEmpty {
fetch()
}
}
func fetch() {
results.removeAll()
isLoading = true
let backendUrl = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as? String ?? ""
let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""
let params = "address=\(escapedAddress)&radius=\(radius)"
let fullUrl = "\(backendUrl)/results?\(params)"
var request = URLRequest(url: URL(string: fullUrl)!)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, _, _ in
if data != nil {
do {
let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data!)
self.results = serviceResponse.results
} catch let jsonError as NSError {
print("JSON decode failed: ", String(describing: jsonError))
}
}
isLoading = false
})
buttonSelected = true
task.resume()
}
var body: some View {
NavigationStack {
if isLoading {
ProgressView()
} else {
VStack {
TextField(
"",
text: $address,
prompt: Text("Search address").foregroundColor(.gray)
)
.onSubmit {
onSubmit()
}
Button(action: onSubmit) {
Text("Submit")
}
.navigationDestination(
isPresented: $buttonSelected,
destination: { ResultView(
address: $address,
results: $results
)
}
)
}
}
}
}
}
So, I tried to move buttonSelected = true right next to isLoading = false within the completion handler for session.dataTask but if I do that ResultView won't be shown. Could it be that state updates are not possible from within completionHandler? If yes, why is that so and what's the fix?
Main Question: How can I change the code above so that the ResultView won't be shown until the API call has finished? (While the API call has not finished yet, I want the ProgressView to be shown).
I think the problem is that the completion handler of URLSession is executed on a background thread. You have to dispatch the UI related API to the main thread.
But I recommend to take advantage of async/await and rather than building the URL with String Interpolation use URLComponents/URLQueryItem. It handles the necessary percent encoding on your behalf
func fetch() {
results.removeAll()
isLoading = true
Task {
let backendUrlString = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as! String
var components = URLComponents(string: backendUrlString)!
components.path = "/results"
components.queryItems = [
URLQueryItem(name: "address", value: address),
URLQueryItem(name: "radius", value: "\(radius)")
]
do {
let (data, _ ) = try await URLSession.shared.data(from: components.url!)
let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data)
self.results = serviceResponse.results
isLoading = false
buttonSelected = true
} catch {
print(error)
// show something to the user
}
}
}
The URLRequest is not needed, GET is the default.
And you can force unwrap the value of the Info.plist dictionary. If it doesn't exist you made a design mistake.
Your fetch() function calls an asynchronous function session.dataTask which returns immediately, before the data task is complete.
The easiest way to resolve this these days is to switch to using async functions, e.g.
func onSubmit() {
if !address.isEmpty {
Task {
do {
try await fetch()
} catch {
print("Error \(error.localizedDescription)")
}
}
}
}
func fetch() async throws {
results.removeAll()
isLoading = true
let backendUrl = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as? String ?? ""
let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""
let params = "address=\(escapedAddress)&radius=\(radius)"
let fullUrl = "\(backendUrl)/results?\(params)"
var request = URLRequest(url: URL(string: fullUrl)!)
request.httpMethod = "GET"
let session = URLSession.shared
let (data, _) = try await session.data(for: request)
let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data)
self.results = serviceResponse.results
isLoading = false
buttonSelected = true
}
In the code above, the fetch() func is suspended while session.data(for: request) is called, and only resumes once it's complete.
From the .navigationDestination documentation:
In general, favor binding a path to a navigation stack for programmatic navigation.
so add a #State var path to your view and use this .navigationDestination initialiser:
enum Destination {
case result
}
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
if isLoading {
ProgressView()
} else {
VStack {
TextField("", text: $address, prompt: Text("Search address").foregroundColor(.gray))
.onSubmit {
onSubmit()
}
Button(action: onSubmit) {
Text("Submit")
}
.navigationDestination(for: Destination.self, destination: { destination in
switch destination {
case .result:
ResultView(address: $address, results: $results)
}
})
}
}
}
}
then at the end of your fetch() func, just set
isLoading = false
path.append(Destination.result)
Example putting it all together
struct Result: Decodable {
}
struct ResultsServiceResponse: Decodable {
let results: [Result]
}
struct ResultView: View {
#Binding var address: String
#Binding var results: [Result]
var body: some View {
Text(address)
}
}
enum Destination {
case result
}
struct ContentView: View {
#State var radius = 10_000 // In meters
#State var isLoading = false
#State private var address: String = ""
#State private var results: [Result] = []
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
if isLoading {
ProgressView()
} else {
VStack {
TextField("", text: $address, prompt: Text("Search address").foregroundColor(.gray))
.onSubmit {
onSubmit()
}
Button(action: onSubmit) {
Text("Submit")
}
.navigationDestination(for: Destination.self, destination: { destination in
switch destination {
case .result:
ResultView(address: $address, results: $results)
}
})
}
}
}
}
func onSubmit() {
if !address.isEmpty {
Task {
do {
try await fetch()
} catch {
print("Error \(error.localizedDescription)")
}
}
}
}
func fetch() async throws {
results.removeAll()
isLoading = true
try await Task.sleep(nanoseconds: 2_000_000_000)
self.results = [Result()]
isLoading = false
path.append(Destination.result)
}
}

It does not fetch data inside onAppear

I have api links that are inside the api link
I have this class and I fetched the data from it so that it gives me the API links
class Api : ObservableObject{
#Published var modelApiLink : [model] = []
func getDataModelApi () {
guard let url = URL(string: APIgetURL.demo) else { return }
var request = URLRequest(url: url)
let token = "38|Xxxxxxx"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, responce, err in
guard let data = data else { return }
do {
let dataModel = try JSONDecoder().decode([model].self, from: data)
DispatchQueue.main.async {
self.modelApiLink = dataModel
}
} catch {
print("error: ", error)
}
}
.resume()
}
}
It's ready now
And I made another function to fetch the API data from the previous one
#Published var models : [model] = []
func getData (url : String) {
// Exactly the same as func last, but needs to put a link
}
And here I presented the data and its work in onAppear
But it does not give any data
struct VideoViewAll : View {
#StateObject var model = Api()
#StateObject var modelApiLink = Api()
‏ var body: some View {
‏ VStack {
‏ ScrollView(.vertical, showsIndicators: false) {
‏
‏
ForEach(model.models) { item in
‏ HStack {
‏ NavigationLink(destination: detiles(model: item)) {
‏ Spacer()
‏ VStack(alignment: .trailing, spacing: 15) {
Text(item.title)
‏ }.padding(.trailing, 5)
}
}
}
‏ .onAppear() {
‏ modelApiLink.getDataModelApi()
‏ for itemModel in modelApiLink.models {
‏ model.getData(url: itemModel.url)
}
}
}
}

WebImage not updating the View - SwiftUI (Xcode 12)

My problem is that when I change my observed objects string property to another value, it updates the JSON Image values printing out the updated values(Using the Unsplash API), but the WebImage (from SDWebImageSwiftUI) doesn't change.
The struct that Result applies to:
struct Results : Codable {
var total : Int
var results : [Result]
}
struct Result : Codable {
var id : String
var description : String?
var urls : URLs
}
struct URLs : Codable {
var small : String
}
Here is the view which includes the webImage thats supposed to update:
struct MoodboardView: View {
#ObservedObject var searchObjectController = SearchObjectController.shared
var body: some View {
List {
VStack {
Text("Mood Board: \(searchObjectController.searchText)")
.fontWeight(.bold)
.padding(6)
ForEach(searchObjectController.results, id: \.id, content: { result in
Text(result.description ?? "Empty")
WebImage(url: URL(string: result.urls.small) )
.resizable()
.frame(height:300)
})
}.onAppear() {
searchObjectController.search()
}
}
}
}
Here is the class which does the API Request:
class SearchObjectController : ObservableObject {
static let shared = SearchObjectController()
private init() {}
var token = "gQR-YsX0OpwkYpbjhPVi3b4kSR-DtWrR5phwDm2kPMM"
#Published var results = [Result]()
#Published var searchText : String = "forest"
func search () {
let url = URL(string: "https://api.unsplash.com/search/photos?query=\(searchText)")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Client-ID \(token)", forHTTPHeaderField: "Authorization")
print("request: \(request)")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
print(String(data: data, encoding: .utf8)!)
do {
let res = try JSONDecoder().decode(Results.self, from: data)
DispatchQueue.main.async {
self.results.append(contentsOf: res.results)
}
//print(self.results)
} catch {
print("catch: \(error)")
}
}
task.resume()
}
}
Here is how I change the value of the searchText in a Button, if you would like to see:
struct GenerateView: View {
#ObservedObject var searchObjectController = SearchObjectController.shared
#State private var celsius: Double = 0
var body: some View {
ZStack {
Color.purple
.ignoresSafeArea()
VStack{
Text("Generate a Random Idea")
.padding()
.foregroundColor(.white)
.font(.largeTitle)
.frame(maxWidth: .infinity, alignment: .center)
Image("placeholder")
Slider(value: $celsius, in: -100...100)
.padding()
Button("Generate") {
print("topic changed to\(searchObjectController.searchText)")
searchObjectController.searchText.self = "tables"
}
Spacer()
}
}
}
}
It turns out you update the search text but don't search again. See this does the trick:
Button("Generate") {
print("topic changed to\(searchObjectController.searchText)")
searchObjectController.searchText.self = "tables" // you changed the search text but didnt search again
self.searchObjectController.search() // adding this does the trick
}
Also. I updated your code to use an EnvironmentObject. If you use one instance of an object throughout your app. Consider making it an EnvironmentObject to not have to pass it around all the time.
Adding it is easy. Add it to your #main Scene
import SwiftUI
#main
struct StackoverflowApp: App {
#ObservedObject var searchObjectController = SearchObjectController()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(self.searchObjectController)
}
}
}
And using it even simpler:
struct MoodboardView: View {
// Env Obj. so we reference only one object
#EnvironmentObject var searchObjectController: SearchObjectController
var body: some View {
Text("")
}
}
Here is your code with the changes and working as expected:
I added comments to the changes I made
struct ContentView: View {
var body: some View {
MoodboardView()
}
}
struct GenerateView: View {
#EnvironmentObject var searchObjectController: SearchObjectController
#State private var celsius: Double = 0
var body: some View {
ZStack {
Color.purple
.ignoresSafeArea()
VStack{
Text("Generate a Random Idea")
.padding()
.foregroundColor(.white)
.font(.largeTitle)
.frame(maxWidth: .infinity, alignment: .center)
Image("placeholder")
Slider(value: $celsius, in: -100...100)
.padding()
Button("Generate") {
print("topic changed to\(searchObjectController.searchText)")
searchObjectController.searchText.self = "tables" // you changed the search text but didnt search again
self.searchObjectController.search() // adding this does the trick
}
Spacer()
}
}
}
}
class SearchObjectController : ObservableObject {
//static let shared = SearchObjectController() // Delete this. We want one Object of this class in the entire app.
//private init() {} // Delete this. Empty Init is not needed
var token = "gQR-YsX0OpwkYpbjhPVi3b4kSR-DtWrR5phwDm2kPMM"
#Published var results = [Result]()
#Published var searchText : String = "forest"
func search () {
let url = URL(string: "https://api.unsplash.com/search/photos?query=\(searchText)")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Client-ID \(token)", forHTTPHeaderField: "Authorization")
print("request: \(request)")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
print(String(data: data, encoding: .utf8)!)
do {
let res = try JSONDecoder().decode(Results.self, from: data)
DispatchQueue.main.async {
self.results.append(contentsOf: res.results)
}
//print(self.results)
} catch {
print("catch: \(error)")
}
}
task.resume()
}
}
struct MoodboardView: View {
// Env Obj. so we reference only one object
#EnvironmentObject var searchObjectController: SearchObjectController
var body: some View {
List {
VStack {
Text("Mood Board: \(searchObjectController.searchText)")
.fontWeight(.bold)
.padding(6)
ForEach(searchObjectController.results, id: \.id, content: { result in
Text(result.description ?? "Empty")
WebImage(url: URL(string: result.urls.small) )
.resizable()
.frame(height:300)
})
}.onAppear() {
searchObjectController.search()
}
// I added your update button here so I can use it.
GenerateView()
}
}
}
struct Results : Codable {
var total : Int
var results : [Result]
}
struct Result : Codable {
var id : String
var description : String?
var urls : URLs
}
struct URLs : Codable {
var small : String
}
Ps. please not that it appears when you search you just append the results to the array and don't delete the old ones. Thats the reason why you still see the first images after updating. The new ones just get appended at the bottom. Scroll down to see them. If you don't want that just empty the array with results upon search

SwiftUI View not updating when Published variable is changed

On a button press, I my app is trying to contact an api to receive data. This data is then stored in a published variable inside an observable object. For some reason, the view doesn't populate with the data until the button that opens that view is press more than once. I am looking for the view to update with the information received from the api call on the first button press. The code I am referencing is provided below:
DataFetcher.swift:
class DataFetcher: ObservableObject{
#Published var dataHasLoaded: Bool = false
#Published var attendeesLoaded: Bool = false
#Published var useresUventsLoaded: Bool = false
#Published var profilesLoaded: Bool = false
#Published var eventsUpdated: Bool = false
#Published var events: [eventdata] = []
#Published var createdEvents: [eventdata] = []
#Published var profile: profiledata?
#Published var atendees: [atendeedata] = []
#Published var IAmAtending: [atendeedata] = []
#Published var eventNames: [eventdata] = []
#Published var profileList: [profiledata] = []
#Published var token: String = UserDefaults.standard.string(forKey: "Token") ?? ""
private var id: Int = 0
func fetchProfile(id: Int){
// events.removeAll()
profileUrl.append("/\(id)")
self.id = id
let url = URL(string: profileUrl)!
var request = URLRequest(url: url)
if let range = profileUrl.range(of: "/\(id)") {
profileUrl.removeSubrange(range)
}
request.httpMethod = "GET"
print(self.token)
request.addValue("Token \(self.token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request, completionHandler: parseFetchProfileObject)
task.resume()
}
func parseFetchProfileObject(data: Data?, urlResponse: URLResponse?, error: Error?){
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else{
print("No data")
return
}
if let decodedResponse = try? JSONDecoder().decode(profiledata?.self, from: content) {
DispatchQueue.main.async {
self.profile = decodedResponse
self.profileList.append(self.profile!)
}
}
}
func fetchAtendees(id: Int){
// events.removeAll()
atendeeUrl.append("/\(id)")
print(atendeeUrl)
let url = URL(string: atendeeUrl)!
var request = URLRequest(url: url)
if let range = atendeeUrl.range(of:"/\(id)") {
atendeeUrl.removeSubrange(range)
}
request.httpMethod = "GET"
print(self.token)
request.addValue("Token \(self.token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request, completionHandler: parseFetchAttendeesObject)
task.resume()
}
EventsUserCreatedView.swift
import Foundation
import SwiftUI
import Mapbox
struct EventsUserCreatedView: View {
#Binding var token: String
#State private var pressedEvent: Bool = false
#State private var selectedEvent: Int = 0
#State private var atendees: [atendeedata] = []
#State private var profileList: [profiledata] = []
#State private var showDeleteEventView: Bool = false
var data: DataFetcher
var mapStyle: URL
var body: some View {
ZStack{
//NavigationView {
if self.mapStyle == MGLStyle.darkStyleURL {
List{
ForEach(self.data.createdEvents){ row in
HStack {
Button("\((row.poi)!)") {
print("Display event information")
self.selectedEvent = row.id
self.pressedEvent = true
}
Spacer()
Button("Delete") {
self.showDeleteEventView = true
print("Deletes the event in this row")
}.buttonStyle(BorderlessButtonStyle())
.padding(4)
.background(Color.red)
.cornerRadius(5)
}.foregroundColor(Color.white)
}
}.background(Color.init(red: 0.05, green: 0.05, blue: 0.05))
//if you hit more buttons than there is room for, it'll be scrollable. make some kind of for loop that iterates over events a user is going to and displays it
// }.navigationBarTitle("My Events")
// .navigationViewStyle(StackNavigationViewStyle())
if pressedEvent{
Group{
if self.data.profilesLoaded == true{
//NavigationView {
List{
ForEach(self.data.profileList){ row in
HStack {
Text("\(row.name)")
.foregroundColor(Color.purple)
Spacer()
}
}
}.background(Color.init(red: 0.05, green: 0.05, blue: 0.05))
//if you hit more buttons than there is room for, it'll be scrollable. make some kind of for loop that iterates over events a user is going to and displays it
//}
} else{
Spacer()
Text("Loading Attendees")
Spacer()
}
}.onAppear{
//this can't be done on appear as it won't update when a different
self.data.profileList = []
self.data.atendees = []
DispatchQueue.main.async{
self.data.fetchAtendees(id: self.selectedEvent)
if self.data.profilesLoaded{
self.profileList = self.data.profileList
self.atendees = self.data.atendees
}
}
}
//.navigationBarTitle("My Attendees")
//.navigationViewStyle(StackNavigationViewStyle())
}
NOTE: datafetcher (the observableobject) is passed to eventsusercreated view by the contentview
any help on how to update my view properly is much appreciated
You've to declare the data as an #ObservedObject.
struct EventsUserCreatedView: View {
//...
#ObservedObject var data = DataFetcher()
//...
}
If you're passing DataFetcher instance as environment object declare it as #EnvironmentObject.
struct EventsUserCreatedView: View {
//...
#EnvironmentObject var data: DataFetcher
//...
}

How to Stop a user from passing the login page if their info is not correct IOS

In my IOS app, I'd like to send a message to a user who tries to login with bad credentials and notify them of this with a pop up. Currently, My database can recognize a login error but my swift code doesn't see the error condition until after it dismisses the login page and enters the app.
The flask/python code that accesses the database looks like this:
#app.route('/login', methods=['GET', 'POST'])
def login():
mydb = mysql.connector.connect(host="localhost", user="root", passwd="Pass", database = "events")
if request.method == 'POST':
mycursor = mydb.cursor()
username = request.form['username']
password = request.form['password']
mycursor.execute('SELECT* FROM accounts WHERE username = %s AND password = %s', (username, password,))
account = mycursor.fetchone()
if account:
try:
mydb.commit()
mydb.close()
except e:
# Rollback in case there is any error
print("Error: ", e)
mydb.rollback()
return make_response("Success!", 200)
else:
return make_response("username/password combination dne", 500)
The swiftui code that contacts the data base inside my app looks like this:
struct LogInView: View {
#State var username: String = ""
#State var password: String = ""
#State var email: String = "test#gmail.com"
#Binding var didLogin: Bool
#Binding var needsAccount: Bool
#State var errorString: String = ""
func send(_ sender: Any, completion: #escaping (String) -> Void) {
let request = NSMutableURLRequest(url: NSURL(string: "http://localhost/login")! as URL)
request.httpMethod = "POST"
self.username = "\(self.username)"
self.password = "\(self.password)"
self.email = "\(self.email)"
let postString = "username=\(self.username)&password=\(self.password)&c=\(self.email)"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
print("error=\(String(describing: error))")
//put variable that triggers error try again view here
self.didLogin = false
self.errorString = String(describing: error)
completion(self.errorString)
return
}else{
self.didLogin = true
completion(String(describing: error))
}
print("response = \(String(describing: response))")
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(String(describing: responseString))")
if let httpResponse = response as? HTTPURLResponse {
self.errorString = String(httpResponse.statusCode)
}
}
task.resume()
}
var body: some View {
VStack{
Spacer()
WelcomeText()
UserImage()
TextField("Username", text: $username)
.padding()
.background(Color(.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("Password", text: $password)
.padding()
.background(Color(.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: {
self.send((Any).self){ array in
self.errorString = array
}/*
if self.errorString == "500"{
self.didLogin = false
}
else{
self.didLogin = true
}
}*/
},
label: {Text("LOGIN")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.orange)
.cornerRadius(15.0)})
.shadow(radius: 5)
.padding(.bottom, 10)
Button(action: {
self.needsAccount = true
}, label: {Text("Not a member yet? Sign up here")})
Spacer()
}.padding().background(Color.white).edgesIgnoringSafeArea(.all)
}
}
ContentView:
import SwiftUI
import Mapbox
import CoreLocation
struct ContentView: View {
#ObservedObject var annotationsVM: AnnotationsVM //= AnnotationsVM()
#ObservedObject var VModel: ViewModel //= ViewModel()
#ObservedObject var locationManager: LocationManager //= LocationManager()
#ObservedObject var data: DataFetcher
// #ObservedObject var mapViewCoordinator = MapViewCoordinator()
init() {
let vm = ViewModel()
VModel = vm
annotationsVM = AnnotationsVM(VModel: vm)
locationManager = LocationManager()
data = DataFetcher()
}
var userLatitude: CLLocationDegrees {
return (locationManager.lastLocation?.latitude ?? 0)
}
var userLongitude: CLLocationDegrees {
return (locationManager.lastLocation?.longitude ?? 0)
}
var lat: Double {
return (VModel.lat ?? 0)
}
var long: Double {
return (VModel.lon ?? 0)
}
var Userlat: Double {
return (VModel.userLatitude)
}
var Userlon: Double {
return (VModel.userLongitude)
}
//#State var searchedLocation: String = ""
#State private var annotationSelected: Bool = false
#State private var renderingMap: Bool = true
#State private var searchedText: String = ""
#State private var showResults: Bool = false
#State private var events: [eventdata] = []
#State private var showMoreDetails: Bool = false
#State private var didLogin: Bool = false
#State private var needsAccount: Bool = false
#State private var selectedAnnotation: MGLAnnotation? = nil
var body: some View {
VStack{
ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)){
MapView(annotationSelected: $annotationSelected, renderingMap: $renderingMap, visited: $annotationsVM.visited, showMoreDetails: $showMoreDetails, selectedAnnotation: $selectedAnnotation, VModel: VModel, locationManager: locationManager, aVM: annotationsVM, data: data, annos: $annotationsVM.annos)
.edgesIgnoringSafeArea(.all)
if showResults == true && searchedText.count >= 1 {
Text("").frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.white).edgesIgnoringSafeArea(.all)
//this is pretty ghetto but whatever
}
VStack{
HStack(alignment: .top){
if showResults == false {
SettingsButton()
}
Spacer()
SearchBar(annotation: annotationsVM, VModel: VModel, searchedText: $searchedText, showResults: $showResults, showMoreDetails: $showMoreDetails)
// SearchBar(annotation: annotationsVM) { sender in
// self.searchedLocation = sender.searchText.text
// }
Spacer()
if showResults == false {
MessageButton()
}
}.padding()
//Update Annotation Button
// Button (action: {
// let delayInSeconds = 1.5
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
// self.annotationsVM.addNextAnnotation(address: "22 Sunset Ave, East Quogue, NY")
//
// print("\(self.annotationsVM.annos)")
// print("User Coords: \(self.VModel.userLatitude), \(self.VModel.userLongitude)")
// }
// }, label: {Text("Press to update annotation")})
if showResults == true && searchedText.count >= 1 {
SearchResults(VModel: VModel, annotation: annotationsVM, showResults: $showResults, searchedText: $searchedText)
}
Spacer()
HStack(alignment: .bottom) {
if renderingMap {
Text("The Map is Rendering...")
}
}
//Side Note: If the Create Event Button is pressed, the currently selected annotation is unselected, so that'll need to be fixed
HStack(alignment: .bottom) {
if annotationSelected {
CreateEventButton(annotation: annotationsVM, annotationSelected: $annotationSelected)
}
}.padding()
}
VStack {
Spacer()
HStack {
Spacer()
if annotationsVM.annotationPlacementFailed == true {
AnnotationPlacementErrorView(annotationPlacementFailed: $annotationsVM.annotationPlacementFailed, annotation: annotationsVM, searchedText: $searchedText)
}
Spacer()
}
Spacer()
}
VStack {
Spacer()
HStack{
Spacer()
if self.showMoreDetails == true {
MoreDetailsView(searchedText: $searchedText, showMoreDetails: $showMoreDetails, selectedAnnotation: $selectedAnnotation)
//Instead of passing in searchedText, we need to pass in the mapView...idk how though
}
Spacer()
}
Spacer()
}
if self.didLogin == false {
LogInView(didLogin: $didLogin, needsAccount: $needsAccount)
}
if self.needsAccount == true {
SignUpView(didLogin: $didLogin, needsAccount: $needsAccount)
}
}
}
}
}
I'm not sure if this is a database//server issue or swiftui/httpresponse issue. Any insight is greatly appreciated
how does it dismiss the login page? assuming you use didLogin to check, you should default didLogin to false. so the user is not logged in until didLogin = true, meaning it will wait till you get a response from your http request. something like this:
#State private var didLogin: Bool = false
if didLogin {
ShowSomeViewAfterLogin()
} else {
ShowLogin()
}

Resources