I am currently updating an App to Swift 3 and iOS 10. The Problem is whenever I use:
self.ref.setValue(value, withCompletionBlock: { (error: Error?, _:FIRDatabaseReference) in
//Code
})
The App crashes without any kind of information on why it does that. If I delete the completion, it works fine.
Try this code, i hope this will do the trick
// U can use this to set value to your database
func setValue() {
let myRef = FIRDatabase.database().reference().child("Your path")
let valueForChild: String = "newValue"
let newValue = ["childName": valueForChild] as [String: Any]
myRef.setValue(newValue) { (error, ref) in
if error != nil {
print(error?.localizedDescription ?? "Failed to update value")
} else {
print("Success update newValue to database")
}
}
}
// or this to update new value to your database
func updateValue() {
let myRef = FIRDatabase.database().reference().child("Your path")
let valueForChild: String = "newValue"
let newValue = ["childName": valueForChild] as [String: Any]
myRef.updateChildValues(newValue) { (error, ref) in
if error != nil {
print(error?.localizedDescription, "Failed to update value")
} else {
print("Success update newValue to database")
}
}
}
Related
I currently have a for loop used to download images from urls stored in arrays. I keep having the issue of seeing the same image after every 2 or 3 new image fetches. Every time I fetch new images, I can see it's using different urls to download images but the images in the arrays are the same.
here is my code below:
func downloadImageDetailsFromFlickr(completion: #escaping (Bool, Error?) -> Void) {
self.imageLoading(is: true)
flickrClient.getImageDetailsFromFlickr(lat: latitude, lon: longitude) { (photo, error) in
if error == nil {
self.photoStore = []
for image in photo {
if image.url_m.count == 0 {
self.imageLabel.isHidden = false
self.imageLoading(is: false)
completion(false, nil)
} else {
self.photoStore.append(image.url_m)
print(self.photoStore)
completion(true, nil)
}
}
} else {
print(error?.localizedDescription ?? "Error in the fetch image block")
print("problem in downloading details.")
completion(false, error)
}
}
}
func downloadImage(completion: #escaping(Bool, Error?) -> Void ) {
let flickrImage = FlickrImage(context: self.dataController.viewContext)
for flickr in photoStore {
self.photoUrl = ""
self.photoUrl = flickr
}
flickrClient.getImage(imageUrl: photoUrl ?? "") { (data, error) in
if error == nil {
self.imageLoading(is: false)
flickrImage.photo = data
flickrImage.url = self.photoUrl
flickrImage.locations = self.location
flickrImage.locations?.latitude = self.latitude
flickrImage.locations?.longitude = self.longitude
}
else {
print(error?.localizedDescription ?? "Something went wrong downloading an image")
}
do {
try self.dataController.viewContext.save()
self.photoStore.removeAll()
completion(true, nil)
} catch {
print("Error saving into Core Data")
completion(false, error)
}
}
}
Please ignore the red box, the white box shows the images being fetched all over again. My guess is, it has to do with core data.
i am not sure if this will fix your problem but, may be you try use dispatchgroup()
so it will goes like this
let group = DispatchGroup()
for flickr in photoStore {
//fetch image from url here
group.enter()
flickrClient.getImage(imageUrl: flickr ?? "") { (data, error) in
if error == nil {
self.imageLoading(is: false)
flickrImage.photo = data
flickrImage.url = self.photoUrl
flickrImage.locations = self.location
flickrImage.locations?.latitude = self.latitude
flickrImage.locations?.longitude = self.longitude
group.leave()
}
else {
print(error?.localizedDescription ?? "Something went wrong downloading an image")
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
//save your dowloaded image here
try self?.dataController.viewContext.save()
self?.photoStore.removeAll()
} catch {
print("Error saving into Core Data")
}
}
so basically by using dispatchgroup you can group the task and run it asynchronously. It just to make sure the request is not repeted and you save the image to core data once.
I am using AWS AppSync for creating my iOS application. I want to leverage the offline mutation as well as query caching provided by AppSync. But when I am turning my internet off, I am not getting any response. Rather its showing an error as "The Internet connection appears to be offline.". This seems to be rather an Alamofire exception than an AppSync exception. This is because the query is not getting cached inside my device. Following is my code snippet to initialize the client.
do {
let appSyncClientConfig = try AWSAppSyncClientConfiguration.init(url: AWSConstants.APP_SYNC_ENDPOINT, serviceRegion: AWSConstants.AWS_REGION, userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider())
AppSyncHelper.shared.appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncClientConfig)
AppSyncHelper.shared.appSyncClient?.apolloClient?.cacheKeyForObject = { $0["id"] }
} catch {
print("Error in initializing the AppSync Client")
print("Error: \(error)")
UserDefaults.standard.set(nil, forKey: DeviceConstants.ID_TOKEN)
}
I am caching the token in the UserDefaults at the time of fetching the session, and then whenever the AppSyncClient is called, it fetches the latest token by calling the getLatestAuthToken() method of my MyCognitoUserPoolsAuthProvider: AWSCognitoUserPoolsAuthProvider. This is returning the token stored in the UserDefaults -
// background thread - asynchronous
func getLatestAuthToken() -> String {
print("Inside getLatestAuthToken")
var token: String? = nil
if let tokenString = UserDefaults.standard.string(forKey: DeviceConstants.ID_TOKEN) {
token = tokenString
return token!
}
return token!
}
My query pattern is the following
public func getUserProfile(userId: String, success: #escaping (ProfileModel) -> Void, failure: #escaping (NSError) -> Void) {
let getQuery = GetUserProfileQuery(id: userId)
print("getQuery.id: \(getQuery.id)")
if appSyncClient != nil {
print("AppSyncClient is not nil")
appSyncClient?.fetch(query: getQuery, cachePolicy: CachePolicy.returnCacheDataElseFetch, queue: DispatchQueue.global(qos: .background), resultHandler: { (result, error) in
if error != nil {
failure(error! as NSError)
} else {
var profileModel = ProfileModel()
print("result: \(result)")
if let data = result?.data {
print("data: \(data)")
if let userProfile = data.snapshot["getUserProfile"] as? [String: Any?] {
profileModel = ProfileModel(id: UserDefaults.standard.string(forKey: DeviceConstants.USER_ID), username: userProfile["username"] as? String, mobileNumber: userProfile["mobileNumber"] as? String, name: userProfile["name"] as? String, gender: (userProfile["gender"] as? Gender).map { $0.rawValue }, dob: userProfile["dob"] as? String, profilePicUrl: userProfile["profilePicUrl"] as? String)
} else {
print("data snapshot is nil")
}
}
success(profileModel)
}
})
} else {
APPUtilites.displayErrorSnackbar(message: "Error in the user session. Please login again")
}
}
I have used all the 4 CachePolicy objects provided by AppSync, i.e,
CachePolicy.returnCacheDataElseFetch
CachePolicy.fetchIgnoringCacheData
CachePolicy.returnCacheDataDontFetch
CachePolicy.returnCacheDataAndFetch.
Can someone help me in implementing the cache properly for my iOS app so that I can do queries without the internet also?
Okay so I found the answer myself. The databaseUrl is an optional argument. It does not come in the suggestions when we are initializing the AWSAppSyncClientConfiguration object.
So the new way in which I initialized the client is the following
let databaseURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(AWSConstants.DATABASE_NAME, isDirectory: false)
do {
let appSyncClientConfig = try AWSAppSyncClientConfiguration.init(url: AWSConstants.APP_SYNC_ENDPOINT,
serviceRegion: AWSConstants.AWS_REGION,
userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider(),
urlSessionConfiguration: URLSessionConfiguration.default,
databaseURL: databaseURL)
AppSyncHelper.shared.appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncClientConfig)
AppSyncHelper.shared.appSyncClient?.apolloClient?.cacheKeyForObject = { $0["id"] }
} catch {
print("Error in initializing the AppSync Client")
print("Error: \(error)")
}
Hope it helps.
This question already has an answer here:
Return a string from a web scraping function in swift
(1 answer)
Closed 4 years ago.
How can I return a value within an if let statement to be further returned within a function? Here is the code:
func loadUrl(url:String) -> String {
DispatchQueue.global().async {
do {
let appUrl = URL(string:url)!
let data = try Data(contentsOf:appUrl)
let json = try JSONSerialization.jsonObject(with: data) as! [String:Any]
print("Test from do")
if let results = json["results"] as? [[String:Any]] {
print("Test from if let 1")
if let first = results[0] as? [String:Any] {
print("Test from if let 2")
var cityStateLocation = first["formatted_address"]!
return cityStateLocation
//What needs to be returned
}
}
DispatchQueue.main.async {
print("No Error")
}
} catch {
DispatchQueue.main.async {
print("Cannot connect to the server.")
}
}
}
}
What I would like to be able to do is take cityStateLocation and return it in the func, but because it is a part of an if let statement within an .async method I don't know how to do that. Could someone please explain?
EDIT: I need the return value of cityStateLocation to equal a variable in a separate function. Here is the separate function:
#IBAction func continueButton(_ sender: Any) {
var cityState:String
if locationSwitch.isOn == true {
print(location.latitude)
print(location.longitude)
let url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=\(location.latitude),\(location.longitude)&result_type=locality&key=AIzaSyDI-ZacHyPbLchRhkoaUTDokwj--z_a_jk"
loadUrl(url: url)
cityState = loadUrl(url: url)
} else {
cityState = ""
}
CoreDataHandler.saveObject(locationLocality: cityState)
}
Edit 2: The main reason why the "duplicate answer" is not a duplicate is that my code needs to call the return of this function within a separate function then save it to Core Data. Also, my code is not using an array.
You could modify your function to include a closure. For instance:
func loadUrl(url: String, completionHandler: #escaping (_ location: String?) -> (Void)) {
And then, where you want to return it, you'd pass it in as such.
completionHandler(cityStateLocation)
I made it an optional so that, in your fail paths, you could return nil.
Then, where you call the function would change. Using trailing closure syntax, it could look like this:
loadUrl(url: "someurl.com/filepath.txt") { optionalLocation in
guard let nonOptionalLocation = optionalLocation else {
// Location was nil; Handle error case here
return
}
// Do something with your location here, like setting UI or something
}
This is a fairly common pattern when dealing with asynchronous activity, such as working with network calls.
The simplest (perhaps no the prettiest), way of doing this would simply be to declare and instantiate a variable above the dispatch queue. Then you can set the variable equal to whatever you want, within the dispatch queue, and return it afterwards. You can change the type of ret, so that it suits your needs more directly.
func loadUrl(url:String) -> String {
var ret = NSObject()
DispatchQueue.global().async {
do {
let appUrl = URL(string:url)!
let data = try Data(contentsOf:appUrl)
let json = try JSONSerialization.jsonObject(with: data) as! [String:Any]
print("Test from do")
if let results = json["results"] as? [[String:Any]] {
print("Test from if let 1")
if let first = results[0] as? [String:Any] {
print("Test from if let 2")
var cityStateLocation = first["formatted_address"]!
ret = cityStateLocation
//What needs to be returned
}
}
DispatchQueue.main.async {
print("No Error")
}
} catch {
DispatchQueue.main.async {
print("Cannot connect to the server.")
}
}
}
return ret
}
DispatchQueue.global().async will cause the coded included in the closure to be executed at some point the future, meaning you loadUrl function will return (almost) immediately.
What you need is some kind of callback which can be called when you have a result (AKA closure)
This is just another way to approach the problem, the difference between this and Josh's example is simply, I provide an additional closure to handle the errors
func loadUrl(url:String, complition: #escaping (String?) -> Void, fail: #escaping (Error) -> Void) {
DispatchQueue.global().async {
do {
let appUrl = URL(string:url)!
let data = try Data(contentsOf:appUrl)
let json = try JSONSerialization.jsonObject(with: data) as! [String:Any]
print("Test from do")
if let results = json["results"] as? [[String:Any]], !results.isEmpty {
print("Test from if let 1")
let first = results[0]
print("Test from if let 2")
if let cityStateLocation = first["formatted_address"] as? String {
complition(cityStateLocation)
} else {
complition(nil)
}
} else {
complition(nil)
}
} catch let error {
fail(error)
}
}
}
Which you might call using something like...
loadUrl(url: "your awesome url", complition: { (value) in
guard let value = value else {
// No value
return
}
// process value
}) { (error) in
// Handle error
}
I have don't know what the problem please help me. When I get a particular message from firebase database then the value is getting but my app got a crash on one line.So please tell me what I do wrong in my code.below is my function.We also provide the screenshot of the error.
func getLatestMessageFromFirebase(token:String,completionmessage: #escaping (_ message:String) -> Swift.Void)
{
print("getModelFromFirebase")
var message:String=""
ref.child("chatmessage/devicetoken/").child(token).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
if value?["message"] as? String != ""
{
DispatchQueue.main.async
{
message = (value?["message"] as? String)! //My app stop on this line
completionmessage(message)
}
}
})
{ (error) in
print(error.localizedDescription)
}
}
func callAPI()
{
let response = (jsonResult.object(forKey: "chatListArr") as? NSArray)!
if response.count > 0
{
for i in 0..<response.count
{
let dict = response[i] as! NSDictionary
let chatlist = ChatList(dict: dict)
self.arr_list.append(chatlist)
}
for i in 0..<self.arr_list.count
{
let chatlist = self.arr_list[i]
self.getLatestMessageFromFirebase(token: chatlist.token, completionmessage: { (message) in
self.arr_list[i].msg = message
})
}
self.table_view.reloadData()
}
}
Please help me.
Thanks in Advance.
First of all you should clean your code up a bit, you do a couple of things which would be considered anti patterns
func getLatestMessageFromFirebase(token:String,completionmessage: #escaping (_ message:String) -> Swift.Void)
{
ref.child("chatmessage/devicetoken/").child(token).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
for snap in snapshot.children.allObjects as [DataSnapshot] {
let value = snap.value as? [String: Any] ?? [:] // A good way to unwrap optionals in a single line
if let message = value["message"] as? String {
DispatchQueue.main.async {
completionmessage(message)
}
}
}
})
{ (error) in
print(error.localizedDescription)
}
}
With the above code your app shouldnt crash. And if there is a message AND it is a string (which might have been your problem before) then your callback will fire.
I'm trying to handle following and unfollowing in my social media app using Firebase. I have a bar button item entitled "Follow". When tapped, it checks the current follow status(retrieved in viewDidLoad), and calls the follow/unfollow methods accordingly. user represents the owner of the page, and the person the currentUser wants to follow/unfollow.
Unexpected behavior: When following a user a second time, you can watch the proper child nodes in the database appear, then disappear. They should not be disappearing. I have refreshed the page to ensure that the nodes are in fact being deleted somehow. It works properly on the first try after every app launch.
Here is my viewDidLoad(responsible for retrieving currentUserIsFollowing). I suspect the issue lies here:
override func viewDidLoad() {
super.viewDidLoad()
let userDogRef = Database.database().reference().child("users").child(user.uid!).child("dogs")
let followingRef = Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("following")
followingRef.observeSingleEvent(of: .childAdded) { (snapshot) in
if snapshot.value == nil {
print("no following found")
return
}
let value = snapshot.value as? NSDictionary
let followingUserUID = String(describing: value!["uid"]!)
if self.user.uid == followingUserUID {
self.currentUserIsFollowing = true
DispatchQueue.main.async {
self.followBarButtonItem.title = "Unfollow"
}
}
}
}
Here is the action called when the Follow/Unfollow button is tapped:
#IBAction func followUserButtonPressed(_ sender: Any) {
if !currentUserIsFollowing {
followUser()
return
}
if currentUserIsFollowing {
unfollowUser()
return
}
}
Here is the followUser() method:
fileprivate func followUser() {
let followingRef = Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("following")
let followersRef = Database.database().reference().child("users").child(user.uid!).child("followers")
followingRef.childByAutoId().updateChildValues(["uid": user.uid as Any]) { (error, ref) in
if error != nil {
print(String(describing: error?.localizedDescription))
}
}
followersRef.childByAutoId().updateChildValues(["uid": Auth.auth().currentUser?.uid as Any]) { (error, ref) in
if error != nil {
print(String(describing: error?.localizedDescription))
}
}
}
Here is the unfollowUser() method:
fileprivate func unfollowUser() {
let followingRef = Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("following")
let followersRef = Database.database().reference().child("users").child(user.uid!).child("followers")
followingRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if snapshot.value == nil {
print("no following found")
}
let value = snapshot.value as? NSDictionary
let followingUserUID = String(describing: value!["uid"]!)
if self.user.uid == followingUserUID {
snapshot.ref.removeValue()
}
})
followersRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if snapshot.value == nil {
print("no followers found")
}
let value = snapshot.value as? NSDictionary
let followerUserUID = String(describing: value!["uid"]!)
if Auth.auth().currentUser?.uid == followerUserUID {
snapshot.ref.removeValue()
}
})
}
Here is a photo of my JSON tree:
There's quite a bit going on here to unpack, but I tried my best to follow along and come up with a solution. For one, instead of having two functions, create a single function that handles following and unfollowing:
#IBAction func followUserButtonPressed(_ sender: Any) {
followOrUnfollow()
}
In that function, listen once to the value of the child you need. Instead of using childByAutoId, use the uid as the key and anything as the value. I just used true. This means you can observe the reference directly instead of having to iterate through all the children looking for the one follower. If the child's data is nil, then the user isn't following yet, so the database is updated to follow. If the child's data is not nil, the data is removed.
func followOrUnfollow() {
let followingRef = Database.database().reference().child("users/\(Auth.auth().currentUser?.uid)!/following/\(user.uid!)")
let followersRef = Database.database().reference().child("users/\(user.uid)!/followers/\(Auth.auth().currentUser?.uid)!")
followingRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value == nil {
print("no following found")
followingRef.updateChildValues([user.uid: "true"]) { (error, ref) in
if error != nil {
print(String(describing: error?.localizedDescription))
}
}
} else {
print("unfollowing")
snapshot.ref.removeValue()
}
})
followersRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value == nil {
print("no followers found")
followersRef.updateChildValues([Auth.auth().currentUser?.uid: "true"]) { (error, ref) in
if error != nil {
print(String(describing: error?.localizedDescription))
}
}
} else {
print("unfollowing")
snapshot.ref.removeValue()
}
})
}
Now there may be some syntactical errors because I'm working on this blindly, but this is the gist of what I would recommend. You will likely have to tweak this to meet your needs.
I'll select Jen's answer as the correct one, but I want to add my working code. I had to make some changes to implement my vision. You can't compare a snapshot.value to nil, so instead you should use if snapshot.exists(). In order to avoid adding a whole new child at the reference point using ref.updateChildValues(), I used .setValue("true"). This just adds a new key-value pair to the "following" and "followers" nodes at the ref.
func followOrUnfollow() {
let followingRef = Database.database().reference().child("users/\(Auth.auth().currentUser!.uid)/following/\(self.user.uid!)")
let followersRef = Database.database().reference().child("users/\(user.uid!)/followers/\(Auth.auth().currentUser!.uid)")
followingRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("no following found")
followingRef.setValue("true") { (error, ref) in
if error != nil {
print(String(describing: error?.localizedDescription))
}
}
} else {
print("unfollowing")
snapshot.ref.removeValue()
}
})
followersRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("no followers found")
followersRef.setValue("true") { (error, ref) in
if error != nil {
print(String(describing: error?.localizedDescription))
}
DispatchQueue.main.async {
self.followBarButtonItem.title = "Unfollow"
}
}
} else {
print("unfollowing")
snapshot.ref.removeValue()
DispatchQueue.main.async {
self.followBarButtonItem.title = "Follow"
}
}
})
}
Here's a picture of my tree: