I am trying to query Firebase to check if any user that has waiting: "1" and then when the snapshot is returned I want to see whether it is equal to nil. I have attempted to do this but the method I have used does not work and I only have some sort of out put if the snapshot is not equal to nil. I have added the code I currently have and the JSON text from Firebase.
import UIKit
import Firebase
import Spring
class GamesViewController: UIViewController {
let ref = Firebase(url: "https://123test123.firebaseio.com")
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
#IBAction func StartGamePressed(sender: AnyObject) {
print("test1")
var peopleWaiting: [String] = []
let userRef = Firebase(url:"https://123test123.firebaseio.com/users")
userRef.queryOrderedByChild("waiting").queryEqualToValue("1")
.observeEventType(.ChildAdded, withBlock: { snapshot in
print(snapshot.key)
if snapshot.key == nil {
print("test2")
let userData = ["waiting": "1"]
let usersRef = self.ref.childByAppendingPath("users")
let hopperRef = usersRef.childByAppendingPath("\(self.ref.authData.uid)")
hopperRef.updateChildValues(userData, withCompletionBlock: {
(error:NSError?, ref:Firebase!) in
if (error != nil) {
print("Data could not be saved.")
self.displayAlert("Oops!", message: "We have been unable to get you into a game, check you have an internet conection. If this problem carries on contect support")
} else {
print("Data saved successfully!")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let Home : UIViewController = storyboard.instantiateViewControllerWithIdentifier("continueToGame")
self.presentViewController(Home, animated: true, completion: nil)
}
})
} else {
var randomUID: String
peopleWaiting.append(snapshot.key)
let randomIndex = Int(arc4random_uniform(UInt32(peopleWaiting.count)))
randomUID = peopleWaiting[randomIndex]
print(randomUID)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let Home : UIViewController = storyboard.instantiateViewControllerWithIdentifier("continueToGame")
self.presentViewController(Home, animated: true, completion: nil)
}
})
}
func displayAlert(title: String, message: String){
let formEmpty = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
formEmpty.addAction((UIAlertAction(title: "Ok", style: .Default, handler: { (action) -> Void in
})))
self.presentViewController(formEmpty, animated: true, completion: nil)
}
func activityIndicatorFunction(){
activityIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 100, 100))
activityIndicator.backgroundColor = UIColor(red:0.16, green:0.17, blue:0.21, alpha:1)
activityIndicator.layer.cornerRadius = 6
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
}
}
JSON Data:
{
"68e42b7f-aea5-4c3f-b655-51a99cb05bb0" : {
"email" : "test1#test1.com",
"username" : "test1",
"waiting" : "0"
},
"8503d5a8-fc4a-492b-9883-ec3664898b4f" : {
"email" : "test2#test2.com",
"username" : "test2",
"waiting" : "0"
}
}
There are a few things going on here, but the most important one is that you cannot test for the existence of children with .ChildAdded. That makes sense if you think about it: the .ChildAdded event is raised when a child is added to the location. If no child is added, the event won't be raised.
So if you want to test if a child exists at a location, you need to use .Value. Once you do that, there are various way to detect existence. Here's one:
ref.queryOrderedByChild("waiting").queryEqualToValue("1")
.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value)
if !snapshot.exists() {
print("test2")
}
});
Check for NSNull. This is the code for observing a node. Queries work much the same way.
Here's a complete and tested app. To use, change the string 'existing' to some path you know exists, like your users path and the 'notexisting' to some path that does not exist
let myRootRef = Firebase(url:"https://your-app.firebaseio.com")
let existingRef = myRootRef.childByAppendingPath("existing")
let notExistingRef = myRootRef.childByAppendingPath("notexisting")
existingRef.observeEventType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
print("This path was null!")
} else {
print("This path exists")
}
})
notExistingRef.observeEventType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
print("This path was null!")
} else {
print("This path exists")
}
})
Please note that by using .Value, there will be a guaranteed a return result and the block will always fire. If your code used .ChildAdded then the block will only fire when a child exists.
Also, check to make sure of how your data appears in firebase.
users
user_0
waiting: 1
if different than
users
user_0
waiting: "1"
Note that "1" is not the same as 1.
Related
so i dont know if the title is right but i want to call an api that inside controller a in controller b. its because i hit the same api and and i figure i just do this
class resetPinVc: UIViewController{
#IBOutlet weak var phoneNumberTextField: SkyFloatingLabelTextField!
#IBAction func doResetPIn(_ sender: UIButton) {
loadingProcess(loading: true) // crash here
phoneNumberTextField.resignFirstResponder()
resetPinviewModel.doResetPin(mobileNumber: phoneNumberTextField.text ?? "") { [weak self] (resetCode) in //crash here
if resetCode.code == 200 {
self?.performSegue(withIdentifier: "timerSegue", sender: self)
self?.loadingProcess(loading: false)
}
else if resetCode.code == 400 {
let message = resetCode.message
self?.showError(message: message)
self?.loadingProcess(loading: false)
}
else if resetCode.code == 403 {
let messages = resetCode.message
self?.showError(message: messages)
self?.loadingProcess(loading: false)
self?.phoneNumberTextField.lineColor = UIColor.init(named: COLOR_RED)!
}
else if resetCode.code == -99 {
let alert = UIAlertController(title: "time out", message: "\(resetCode.code)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ok", style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
}
}
}
so i want to call that func that hit api in another controller....which is like this
class ResetTimerVc: UIViewController {
let resetPinVC = resetPinVc()
#IBAction func resend(_ sender: UIButton) {
let timeFormated = resetPinViewModel.timeFormatted(totalSeconds: totalTime)
timer?.invalidate()
totalTime = 60
timerLabel.text = timeFormated
timerIsOn = false
if timerIsOn == false {
resendButton.isEnabled = false
resendButton.setTitleColor(UIColor.red, for: .normal)
startTimer()
}
resetPinVC.doResetPIn(resendButton)
}
}
but turns out i have a crash (in the line i mention) because apparently i dont have the that loadingProcess func on my ResetTimerVc, and i dont have phoneNumberTextField on my ResetTimerVc as well.
any idea how do i get over this? sorry for the bad english
the reason i need to hit the same api its because i need to hit that api with the same data that i fill in ResetPinVc on ResetTimerVc
thanks.
I am parsing a certain json url data to plot in a map and I need to detect that I have all the data to show a spinner while nothing is happening. I have created a variable that goes from false to true after I have all the data but that variable only exists as true inside the for loop
This is part of the code that gets the data
import SwiftUI
import MapKit
var locationsFillTest : Int = 0
var allLocations = [MKPointAnnotation]()
var doneGettingData : Bool = false
struct MapView: UIViewRepresentable {
var startdate : String
func makeUIView(context: Context) -> MKMapView{
MKMapView(frame: .zero)
}
func makeCoordinator() -> MapViewCoordinator{
MapViewCoordinator(self)
}
func updateUIView(_ uiView: MKMapView, context: Context){
uiView.removeAnnotations(allLocations)
allLocations = []
doneGettingData = false
print("Done = \(doneGettingData)")
let url = URL(string: "https://XXXXXX")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(emsc.self, from: d)
DispatchQueue.main.async {
locationsFillTest = allLocations.count
doneGettingData = false
for locations in decodedLists.features {
let lat = Double(locations.properties.lat)
let long = Double(locations.properties.lon)
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: lat , longitude: long )
if locationsFillTest == 0 {
allLocations.append(annotation)}
}
uiView.addAnnotations(allLocations)
uiView.delegate = context.coordinator
uiView.showAnnotations(allLocations, animated: true)
doneGettingData = true
print("Done = \(doneGettingData)")
}
}else {
print("No Data")
}
} catch {
print("Error decoding JSON: ", error, response!)
}
}.resume()
}
}
The variable doneGettingData becomes false and true by watching the print but if I need to use it for example to create a spinner its false all the time since its only true inside.
How can I make it global ?
Thank you
Unless you have another declaration for doneGettingData inside the closure the instance level property is getting set to true. It may be getting set later than you expect though. Try the following to see when it changes (and to get you setup to react to those changes):
var doneGettingData : Bool = false {
didSet {
if doneGettingData {
print("The instance property doneGettingData is now true.")
} else {
print("The instance property doneGettingData is now false.")
}
}
}
You may want to make this into a custom enum though with cases along the lines of fetching, done, noData, and jsonError. Right now if there is no data you will never have a trigger to either retry, move on, notify the user, etc. The same applies when there is a decoding error. Or at the very least set the flag to true at the very end of the loop so something happens no matter what.
Something like:
enum DataCollectionState {
case fetching, done, noData, jsonError
var doneGettingData : DataCollectionState = fetching {
didSet {
switch doneGettingData {
case fetching:
// Show a spinner or something
case done:
// Hide the spinner
case noData:
// Tell the user there was no data? Try again?
case jsonError:
// Tell the user there was an error? Try again?
}
}
}
Note: I don't have Xcode open right now so syntax may not be exact.
I have a problem in modifying the video format after downloading from videoplayback to mp4 and save to camera.
This is my download code, but I downloaded some video with a different format example : "videoplayback". I can't save to camera because I want to change format video to mp4.
func SessionDownload(URLSession : String) {
MBProgressHUD.hideAllHUDs(for: view, animated: true)
let hud = MBProgressHUD.showAdded(to: self.view, animated: true)
// Set the bar determinate mode to show task progress.
progress = 0.0
hud?.mode = MBProgressHUDMode.determinateHorizontalBar
hud?.isUserInteractionEnabled = true;
hud?.labelText = NSLocalizedString("Downloading...", comment: "HUD loading title")
DispatchQueue.global(qos: .default).async(execute: {() -> Void in
// Do something useful in the background and update the HUD periodically.
self.doSomeWorkWithProgress()
DispatchQueue.main.async(execute: {() -> Void in
//hud?.hide(true)
hud?.labelText = NSLocalizedString("Just Wait...", comment: "HUD loading title")
})
})
let videoPath = URLSession
print(videoPath)
let s = videoPath
let url = NSURL(string:s)!
let req = NSMutableURLRequest(url:url as URL)
let config = URLSessionConfiguration.default
let task = self.session.downloadTask(with: req as URLRequest)
self.task = task
task.resume()
}
//MARK:- share video
func doSomeWorkWithProgress() {
// This just increases the progress indicator in a loop.
while progress < 1.0 {
DispatchQueue.main.async(execute: {() -> Void in
print(self.progress)
MBProgressHUD(for: self.view).progress = self.progress
})
usleep(50000)
}
}
//MARK:- URL Session delegat
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print("downloaded \(100*totalBytesWritten/totalBytesExpectedToWrite)")
taskTotalBytesWritten = Int(totalBytesWritten)
taskTotalBytesExpectedToWrite = Int(totalBytesExpectedToWrite)
percentageWritten = Float(taskTotalBytesWritten) / Float(taskTotalBytesExpectedToWrite)
print(percentageWritten)
let x = String(format:"%.2f", percentageWritten)
print(x)
self.progress = Float(x)!
print(progress)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
// unused in this example
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("completed: error: \(error)")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Finished downloading!")
let fileManager = FileManager()
// this can be a class variable
let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
print(directoryURL)
let docDirectoryURL = NSURL(fileURLWithPath: "\(directoryURL)")
print(docDirectoryURL)
//Save To Photos
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL:directoryURL)
}) { saved, error in
if saved {
let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
let destinationFilename = downloadTask.originalRequest?.url?.lastPathComponent
print(destinationFilename!)
// append that to your base directory
let destinationURL = docDirectoryURL.appendingPathComponent("\(destinationFilename!)")
print(destinationURL!)
/* check if the file exists, if so remove it. */
if let path = destinationURL?.path {
if fileManager.fileExists(atPath: path) {
do {
try fileManager.removeItem(at: destinationURL!)
} catch let error as NSError {
print(error.debugDescription)
}
}
}
do
{
try fileManager.copyItem(at: location, to: destinationURL!)
}
catch {
print("Error while copy file")
}
DispatchQueue.main.async(execute: {() -> Void in
MBProgressHUD.hide(for: self.view, animated: true)
})
// let videoLink = NSURL(fileURLWithPath: filePath)
let objectsToShare = [destinationURL!] //comment!, imageData!, myWebsite!]
let activityVC = UIActivityViewController(activityItems: objectsToShare , applicationActivities: nil)
activityVC.setValue("Video", forKey: "subject")
//New Excluded Activities Code
if #available(iOS 9.0, *) {
activityVC.excludedActivityTypes = [UIActivity.ActivityType.airDrop, UIActivity.ActivityType.addToReadingList, UIActivity.ActivityType.assignToContact, UIActivity.ActivityType.copyToPasteboard, UIActivity.ActivityType.mail, UIActivity.ActivityType.message, UIActivity.ActivityType.openInIBooks, UIActivity.ActivityType.postToTencentWeibo, UIActivity.ActivityType.postToVimeo, UIActivity.ActivityType.postToWeibo, UIActivity.ActivityType.print]
} else {
// Fallback on earlier versions
activityVC.excludedActivityTypes = [UIActivity.ActivityType.airDrop, UIActivity.ActivityType.addToReadingList, UIActivity.ActivityType.assignToContact, UIActivity.ActivityType.copyToPasteboard, UIActivity.ActivityType.mail, UIActivity.ActivityType.message, UIActivity.ActivityType.postToTencentWeibo, UIActivity.ActivityType.postToVimeo, UIActivity.ActivityType.postToWeibo, UIActivity.ActivityType.print ]
}
if let popoverController = activityVC.popoverPresentationController {
popoverController.sourceView = self.BtnDownloadVideo
popoverController.sourceRect = self.BtnDownloadVideo.bounds
}
self.present(activityVC, animated: true, completion: nil)
}
I'm assuming when you download a file from the internet, you are sure you are downloading a video in this circumstance? And what you are really wanting is just to change the format, i.e. PathExtension such as .mp4, .png, jpeg, etc.
Iff (if and only if) this is the case, then you can add a file extension on to the path component.
let destinationURL = docDirectoryURL.appendingPathComponent("\(destinationFilename!)").appendingPathExtension("mp4")
Now, when you check your saved files, it will include the ".mp4"
Again, I'm assuming you are 110% confident you are downloading a ".mp4" from the interwebs.
I'm trying to add annotations on the map using a list of locations coming from the server.
Json Data:
[{ "_id" : "589f3e299b64795df23a886b",
"name" : "Store 1",
"description" : "Most awesome store!",
"location" : {
"longitude" : -6.279025369998967,
"latitude" : 53.35487382895707
}
},
{
"_id" : "589f3e299b64795df23a886b",
"name" : "Store 2",
"description" : "Most awesome store!",
"location" : {
"longitude" : -6.267085527951536,
"latitude" : 53.33785738724761
}
}]
I am using alamo fire to send a get request from the server to get the json data above in the viewWillAppear method.
override func viewWillAppear(_ animated: Bool) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
mapView.showsUserLocation = false
var currentLocation = Location()
if let location = locationManager.location {
currentLocation = Location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
}
//default to 10 km radius for now
let params = [
"latitude": currentLocation.getLatitude(),
"longitude": currentLocation.getLongitude(),
"radius": 10.0
] as [String : Any]
Alamofire.request("http://website.com/fyp/searchNearbyStores", parameters: params).responseJSON { response in
switch response.result {
case .success(let value):
let result = JSON(value)
print(result)
self.storeList = result.arrayValue.map({
Store(name: $0["name"].stringValue, description: $0["description"].stringValue, location: CLLocationCoordinate2D(latitude: $0["location"]["latitude"].doubleValue, longitude: $0["location"]["longitude"].doubleValue))
})
case .failure(let error):
print(error)
}
}
}
And finally in the viewDidLoad method I use the storeList with the lat/long data to add the annotations.
override func viewDidLoad() {
super.viewDidLoad()
//zoom to current user location
if let location = locationManager.location {
let span = MKCoordinateSpanMake(0.01, 0.01)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
for store in storeList {
let annotation = MKPointAnnotation()
annotation.title = store.getName()
annotation.subtitle = store.getDescription()
annotation.coordinate = store.getLocation()
mapView.addAnnotation(annotation)
}
}
For some reason the annotations doesnt seem to show in the map when I run it. I seem to be doing the right way of adding annotations but maybe missed a small but important bit. Can anyone help?
You are using viewWillAppear wrong. You should make a request in viewDidLoad and do this inside request function (after data is loaded)
if let location = locationManager.location {
let span = MKCoordinateSpanMake(0.01, 0.01)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
for store in storeList {
let annotation = MKPointAnnotation()
annotation.title = store.getName()
annotation.subtitle = store.getDescription()
annotation.coordinate = store.getLocation()
mapView.addAnnotation(annotation)
}
because viewDidLoad is called before viewWillAppear, and because Alamofire request is async, which means, that it will be finished after viewDidLoad and viewWillAppear. So what happens is your array is empty, when you add annotations, and when your data is downloaded - nothing happens
I have an application where I need to validate some information(zip code) from a database before I allow my iOS application to proceed to the next view. I used the zip code project to import a DB Table will all valid US Zip codes, and I want to have the zip code the inputed by the user validated before I allow them to proceed. If the zip code isn't valid, I hold them up at the current view and display an alert. I have a class to validate the zip code, but the zip code isn't being validated until after the next view is loaded. I've been leaning towards using a completion handler, but I'm not exactly sure if that's my best/only option. Thanks in advance.
EDIT:
The following is the whole class for retrieve the data
protocol ZipCodeLocationProtocol: class {
func zipCodeLocationDownloaded(zipLocation: Location)
}
class RetrieveZipCodeLocation: NSObject, NSURLSessionDataDelegate {
// MARK: Properties
weak var delegate: ZipCodeLocationProtocol!
var data: NSMutableData = NSMutableData()
let urlPath: String = "xxxx"
func downloadZipCodeLocation(zipcode: Int) {
let path = self.urlPath + "?zipcode=\(zipcode)"
let url: NSURL = NSURL(string: path)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
var location = Location(title: "TITLE", coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0))
do {
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:[]) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
for(var i = 0; i < jsonResult.count; i++) {
jsonElement = jsonResult[i] as! NSDictionary
let point = CLLocationCoordinate2D(latitude: (jsonElement["LATITUDE"] as! NSString).doubleValue, longitude: (jsonElement["LONGITUDE"] as! NSString).doubleValue)
// Get Information
location = Location(title: "TITLE", coordinate: point)
self.delegate.zipCodeLocationDownloaded(location)
}
}
I'm going to assume that a button triggers the segue to the next view. I'm also going to assume that the button is hooked up to a function for target-action. I'm also going to assume that you have the code to get the zip codes, otherwise you'll have to ask a separate question for that.
Assumptions aside, you need to present a UIAlertController instead of going to the next view controller when tapping the button. In order to do that:
func buttonAction() {
if verifyZipCode() {
let alert = UIAlertController(title: "Hold Up", message: "That zip code is invalid.", preferredStyle: .Alert)
let fixIt = UIAlertAction(title: "Fix It!", style: .Default, handler: nil) // handler could also contain code to make text field red or something interesting
alert.addAction(fixIt)
presentViewController(alert, animated: true, completion: nil)
} else {
// existing segue code
}
}
func verifyZipCode() -> Bool {
// Take text field text and verify zip code
}