Sending data to 3D Touch shortcut - swift

I am building a weather application and I want to be able to have weather data (e.g. Temperature) be seen when a user activates a shortcut menu (via 3D Touch on the home screen). I would like the weather data to show up in the shortcut so the user does not have to enter the application to check the temperature. Here is the code used to retrieve the weather data, I will post more code if need be:
struct ForecastService {
let forecastAPIKey: String
let forecastBaseURL: NSURL?
init(apiKey: String) {
forecastAPIKey = apiKey
forecastBaseURL = NSURL(string: "https://api.forecast.io/forecast/\(forecastAPIKey)/")
}
func getForecast(lat: Double, long: Double, completion: (CurrentWeather? -> Void)) {
if let forecastURL = NSURL(string: "\(lat),\(long)", relativeToURL: forecastBaseURL) {
let networkOperation = NetworkOperation(url: forecastURL)
networkOperation.downloadJSONFromURL {
(let JSONDictionary) in
let currentWeather = self.currentWeatherFromJSON(JSONDictionary)
completion(currentWeather)
}
} else {
print("Could not construct a valid URL")
}
}
func currentWeatherFromJSON(jsonDictionary: [String: AnyObject]?) -> CurrentWeather? {
if let currentWeatherDictionary = jsonDictionary?["currently"] as? [String: AnyObject] {
return CurrentWeather(weatherDictionary: currentWeatherDictionary)
} else {
print("JSON Dictionary returned nil for 'currently' key")
return nil
}
}
}//end struct

You should create a UIApplicationShortcutItem with its title set to the weather conditions you want to show, then set your application’s shortcutItems to an array containing that item. For example:
let item = UIApplicationShortcutItem(type:"showCurrentConditions", localizedTitle:"64° Sunny")
UIApplication.sharedApplication().shortcutItems = [item]
Note that the “type” parameter is an arbitrary string—your app delegate just needs to be able to recognize it when the user selects the shortcut.

Related

Else on If Else statement won't get triggered, can't understand why

I have this block of code:
func fetchFriends() {
if let window = UIApplication.shared.keyWindow {
guard let userId = Auth.auth().currentUser?.uid else { return }
DispatchQueue.main.async {
FirestoreService.shared.fetchFriendList(userId) { (fetchedFriends) in
//// WONT GET HERE ////
if fetchedFriends != nil {
self.fetchedFriends = fetchedFriends! // Can force unwrap here because we already know that fetchedFriends in not nil.
self.friendsTable.reloadData()
}else {
self.fetchedFriends = []
self.friendsTable.reloadData()
}
}
}
}
}
This block of code is using this function:
func fetchFriendList(_ id: String, completion: #escaping([Friend]?)->()) {
var fetchedFriends: [Friend] = []
db.collection(USERS_COLLECTION).document(id).getDocument { (doc, err) in
if err == nil && doc != nil {
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
for result in results { // Getting the data in firebase
if let resultValue = result.value as? [String: Any] { // Getting only the value of the MAP data, we do not need the key.
//Getting the fields from the result
guard let id = resultValue[FRIEND_ID] as? String else { return }
guard let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String else { return }
guard let username = resultValue[FRIEND_NAME] as? String else { return }
guard let email = resultValue[FRIEND_MAIL] as? String else { return }
//Creating a new Friend object from the fields
let friend = Friend(id: id, profilePicture: profilePic, username: username, email: email)
fetchedFriends.append(friend)
}
completion(fetchedFriends)
}
}else {
print(err!.localizedDescription)
completion(fetchedFriends)
}
}
}
Whats happening here, is that I'm going into a user's document, getting it's 'Friends' from a Map I have in the document, creating a Friend Array and sending it in the completion to the first function.
In the first function, I'm checking if what I got is nil, if not, I'm assigning it to an array, else, if it is nil, I want the array to be empty.
The purpose here is to show the "Friends" in the tableView if the user has any.
My problem is this situation:
For start, the list of friends is empty, adding a friend and viewing the list, the friend I just added is showing, which is good. the problem is, when I'm removing this friend from the list (and it is deleted in the Database in Firestore), showing the list again does not deletes him from the list and still showing it.
It seems that after removing a friend from the "following" section, and showing the list again, after FirestoreService.shared... it just returns and won't get to the "Won't get here" line.
The FetchFriends() function does gets called everytime I'm opening the FriendsList.
This is a picture of the list I'm referring to, this demouser is removed from the friends list but still showing up.
EDIT: Just noticed that when I have more than one user on the list, it does gets deleted and it works as I want. When I have just one user (or just one left on the list) it won't delete it.
fetchFriendList never calls the callback with a nil value:
var fetchedFriends: [Friend] = []
Therefore your else branch is unnecessary and the completion handler could be #escaping ([Friend]) -> Void without optionals.
By the way, there is also a situation when your method does not call completion at all:
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
In general, there are many unsafe places. For example, when err is nil and doc is nil, then your else will crash unwraping err!.
A better alternative:
guard err == nil, let doc = doc else {
print(err?.localizedDescription)
completion([])
return
}
let results = (doc.data()?[USER_FOLLOWING] as? [String: Any]) ?? [:]
let fetchedFriends = results.compactMap { result in
guard
let resultValue = result.value as? [String: Any],
let id = resultValue[FRIEND_ID] as? String,
let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String,
let username = resultValue[FRIEND_NAME] as? String,
let email = resultValue[FRIEND_MAIL] as? String
else { return nil }
return Friend(id: id, profilePicture: profilePic, username: username, email: email)
}
completion(fetchedFriends)

FlickrAPI trouble understanding

I have to make an app that has the user place a pin on a map and then pulls pictures from Flickr based off the pin's location. My professor gave us a FlickrAPI file to use but I am having trouble understanding what it is doing.
import Foundation
class FlickrAPI: NSObject {
static let sharedInstance = FlickrAPI() // Create a Singleton for the class
var photos: Array<String> = []
var errorLevel = 0
// Flickr Constants
let BASE_URL = "https://api.flickr.com/services/rest/"
let METHOD_NAME = "flickr.photos.search"
let API_KEY = "<API Key Here>"
func fetchPhotosFromFlickrBasedOn(Latitude lat: Double, Longitude lng: Double, PageToFetch pageToFetch: Int, completion: #escaping (_ error: Int, _ pg: Int, _ pgs: Int) -> Void) {
// Empty our Photos Array
photos.removeAll(keepingCapacity: true)
// Build Aurgument List
let methodArguments = [
"method": METHOD_NAME,
"api_key": API_KEY,
"lat": String(format: "%f", lat),
"lon": String(format: "%f", lng),
"accuracy": "15", // Accuracy (Street Level)
"radius": "1", // Distance
"radius_units": "km", // in Kilometers,
"safe_search": "1", // Safe (G Rated),
"content_type": "1", // Photos Only
"per_page": "100", // Photos per Page
"page": "\(pageToFetch)",
"extras": "url_m", // Return Photo URLs
"format": "json", // Request JSON data format
"nojsoncallback": "1" // No JSON Callback
]
// Initialize Shared Session
let session = URLSession.shared
let url = URL(string: BASE_URL + escapeUrlParameters(methodArguments))!
let request = URLRequest(url: url)
var page = 0
var pages = 0
// Setup Session Handler
let task = session.dataTask(with: request, completionHandler: {data, response, error in
self.errorLevel = 0 // Initialize Error Level
if error != nil {
self.errorLevel = 1 //***** Network Error
} else {
// Okay to Parse JSON
do {
let parsedResult: AnyObject = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as AnyObject
if let photosDictionary = parsedResult.value(forKey: "photos") as? NSDictionary {
if let photoArray = photosDictionary.value(forKey: "photo") as? [[String: AnyObject]] {
page = photosDictionary["page"] as! Int
pages = photosDictionary["pages"] as! Int
for photoDictionary in photoArray {
if let photoUrl = photoDictionary["url_m"] as? NSString {
let ext = photoUrl.pathExtension
let noExt = photoUrl.deletingPathExtension
let addThumbDesignation = (noExt + "_q_d") as NSString
let thumbUrl = addThumbDesignation.appendingPathExtension(ext)
self.photos.append(thumbUrl!)
} else {
NSLog("***** Could not obtain an Image URL at Index:%d for Owner:%#", self.photos.count, photoDictionary["owner"] as! String)
}
}
} else {
self.errorLevel = 4 //***** No "Photo" Array key present
}
} else {
self.errorLevel = 3 //***** No "Photos" Dictionary key present
}
} catch {
self.errorLevel = 2 //***** Parsing Error
}
completion(self.errorLevel, page, pages)
}
})
task.resume()
}
// Escape URL Parameters
func escapeUrlParameters(_ parms: [String : String]) -> String {
var urlParms = [String]()
for (key, value) in parms {
let escapedValue = value.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
urlParms += [key + "=" + "\(escapedValue!)"]
}
return urlParms.isEmpty ? "" : "?" + urlParms.joined(separator: "&")
}
}
In my code I have this function:
#IBAction func addPin(_ gestureRecognizer: UILongPressGestureRecognizer) {
var touchPoint = gestureRecognizer.location(in: Map)
var newCoordinates = Map.convert(touchPoint, toCoordinateFrom: Map)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinates
Map.addAnnotation(annotation)
flickr.fetchPhotosFromFlickrBasedOn(Latitude: annotation.coordinate.latitude, Longitude: annotation.coordinate.longitude, PageToFetch: 1, completion: {error,pg,pgs in
print("Error: \(error)")
print("Page: \(pg)")
print("Pages: \(pgs)")
})
//flickr.escapeUrlParameters(<#T##parms: [String : String]##[String : String]#>)
}
I understand that the code should be sending json information that I am supposed to use to display the image results on my app but like I said I am having a hard time understanding my professor's code. My main problem is after calling the function it skips everything that needs to run to pull the photos. I am clearly missing what I need to have so that my code will execute what it needs.
The radius parameter you had in FlickrAPI specified a radius that was not valid for Flickr's API. It should be something lower than 50. You'll have to play around with to see what the max you can get away with is, if that's the route you want to go.
I would recommend using Postman to test URLs, rather than fiddling around debugging API issues in Xcode. Get your API call-oriented poop in a group before making calls in Xcode. It's a lot easier that way. In this case, just put a breakpoint at wherever the URL is, type po url in the debugger, copy the URL into Postman and see what comes back in Postman. API's like Flickr are pretty good at telling you what ****ed up. Also, don't be afraid to look at documentation for stuff. It makes life a lot easier. The documentation for this endpoint is located here.
When you get to the completion call, put another breakpoint and type po photos. It'll print out an array of strings, which are URLs for the photos.
You probably don't need the location manager or the usage description thing in your info.plist if you're just tapping on the map. Lastly, don't chuck an API key out there in an open forum like GitHub or SO.

Why .childAdded is not called when new data is added? Firebase

I am trying to read data from media when data is updated on /media node, but .observe(.childAdded is not called.
For example, I update data at /media/-LKN1j_FLQuOvnhEFfao/caption , but I never receive the event in observeNewMedia .
I can read the data with no problem the first time when ViewDidLoad completes.
The first step is to download the user data, second is to get the locality from currentUser and the last step is to attach a listener .childAdded on media.
I suspect that the event is not triggered because fetchMedia is called inside DDatabaseRReference.users(uid: uid).reference().observe(.value
media
-LKNRdP4ZsE3YrgaLB30
caption: "santa"
mediaUID: "-LKNRdP4ZsE3YrgaLB30"
locality: "barking"
users
Q6Dm3IMLNLgBH3ny3rv2CMYf47p1
media
-LKNReJCxgwtGRU6iJmV: "-LKNRdP4ZsE3YrgaLB30"
email: "john#gmail.com"
locality: "barking"
//enables the programmer to create references to different childs in Firebase
enum DDatabaseRReference {
case root
case users(uid:String)
case media //to store photos
func reference() -> DatabaseReference {
return rootRef.child(path)
}
//return root reference to our database
private var rootRef: DatabaseReference {
return Database.database().reference()
}
private var path: String {
switch self { //self is the enum DDatabaseReference
case .root:
return ""
case .users(let uid):
return "users/\(uid)"
case .media:
return "media"
}
}
}//end of enum DatabaseReference
class NewsfeedTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//observe ~/users/uid
DDatabaseRReference.users(uid: uid).reference().observe(.value, with: { (snapshot) in
DispatchQueue.main.async {
if let userDict = snapshot.value as? [String : Any] {
self.currentUser = UserModel(dictionary: userDict)
self.fetchMedia()
self.tableView.reloadData()
}
}
})
}
func fetchMedia() {
Media.observeNewMedia((currentUser?.locality)!) { (newMedia) in
//check if newly downloaded media is already in media array
if !self.media.contains(newMedia) {
self.media.insert(newMedia, at: 0)
self.tableView.reloadData()
}else {
//remove old media and add the newly updated one
guard let index = self.media.index(of: newMedia) else {return}
self.media.remove(at: index)
self.media.insert(newMedia, at: 0)
self.tableView.reloadData()
}
}
}
}//end of NewsfeedTableViewController
class Media {
class func observeNewMedia(_ userLocality: String, _ completion: #escaping (Media) -> Void) {
DDatabaseRReference.media.reference().queryOrdered(byChild: "locality").queryEqual(toValue: userLocality).observe(.childAdded, with: { snapshot in
guard snapshot.exists() else {
print("no snap ")
return}
print("snap is \(snapshot)")
let media = Media(dictionary: snapshot.value as! [String : Any])
completion(media)
})
}
} //end of class Media
Let's first update the structure so make it more queryable
assume a users node
users
-Q6Dm3IMLNLgBH3ny3rv2CMYf47p1 //this is each users uid
email: "john#gmail.com"
locality: "barking"
and a media node that contains media for all users
media
-abcdefg12345 //node created with childByAutoId
caption: "santa"
for_uid: -Q6Dm3IMLNLgBH3ny3rv2CMYf47p1 //matches the uid in the /users node
Then our main viewController which contains a reference to Firebase and logs the user in
class ViewController: UIViewController {
var ref: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
self.ref = Database.database().reference()
//log user in which will populate the Auth.auth.currentUser variable
}
.
.
.
We need an object to store the media in and then an array to hold those objects
class MediaClass {
var key = ""
var caption = ""
init(k: String, c: String) {
self.key = k
self.caption = c
}
}
var mediaArray = [MediaClass]()
then set up the observers which will add, update or remove from the array when media for this user is added, changed or removed.
let thisUid = Auth.auth().currentUser?.uid
let mediaRef = self.ref.child("media")
let queryRef = mediaRef.queryOrdered(byChild: "for_uid").queryEqual(toValue: thisUid)
queryRef.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let key = snapshot.key
let caption = dict["caption"] as! String
let m = MediaClass.init(k: key, c: caption)
self.mediaArray.append(m)
self.tableView.reloadData()
})
queryRef.observe(.childChanged, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let key = snapshot.key
let caption = dict["caption"] as! String
let index = self.mediaArray.index { $0.key == key } //locate this object in the array
self.mediaArray[index!].caption = caption //and update it's caption
self.tableView.reloadData()
})
//leaving this an an exercise
queryRef.observe(.childRemoved....
Note we added .childAdded, .childChanged and .childRemoved events to the media node via a query so the only events the app will receive are the ones that pertain to this user.
Also note there's no error checking so that needs to be added.

Showing just one image at the time from database (Swift and Firebase)

First of all, I am new in this, so please do not make fun of me :)
Basically, I am trying to show and Image of a product but if the client refuses the product this item will not appear on his account. That is why I am creating another table Rejected (setAcceptedOrRejected) where I put the ID of the product and the Id of the client so I wont see the item he rejected before.
What I tried here it was to get the List (Good) with all the items and the (Bad) with the rejected items. Then compare it to display the picture of the item again.
My problem is that I want to show only 1 picture at the time, if the client refuses then it will show the next one and so on but it wont show that picture again.
I hope you can really help me with this one.
Thank you
func updateImage() {
createListProductsBad ()
var badnot = ""
for bad2 in listProductsBad{
badnot = bad2
}
Database.database().reference().child("Products").child(bad2)queryOrderedByKey().observe(.childAdded, with: { snapshot in
let userInfo = snapshot.value as! NSDictionary
let storageRef = Storage.storage().reference(forURL: profileUrl)
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
self.productPhoto.image = image
}
catch _ {
print("error")
}
})
})
}
func setAcceptedOrRejected() {
let notThankyou = [ "ProductID": ProductId,
"UserID": userUID
] as [String : Any]
self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.refProducts.child("Rejected").childByAutoId().setValue(notThankyou)
}
func createListProductsGood () {
Database.database().reference().child("Products").queryOrderedByKey().observe(.childAdded, with: { snapshot in
if !snapshot.exists() { return }
let userInfo = snapshot.value as! NSDictionary
let goodID = String(snapshot.key)
for prod in self.listProductsBad{
if (prod == goodID){
print("Not good **********************")
}else{
if (goodID != "" ){
self.listProductsGood.append(prod)
}
}
}
})
}
func createListProductsBad () {
Database.database().reference().child("Rejected").queryOrderedByKey().observe(.childAdded, with: { snapshot in
let userInfo = snapshot.value as! NSDictionary
let currentID = userInfo["UserID"] as! String
let badProduct = userInfo["ProductID"] as! String
if (self.userUID == currentID ){
self.listProductsBad.append(badProduct)
}
})
}
}
//These can also be swift's dictionaries, [String: AnyObject] or possibility arrays if done correctly. All depends on your style of programming - I prefer NSDictionaries just because.
let availableKeys: NSMutableDictionary = [:]
let rejectedKeys: NSMutableDictionary = [:]
//Might be a better way for you. Depends on what you are looking for.
func sortItems2() -> NSMutableDictionary{
for rejKey in rejectedKeys.allKeys{
//Removes if the rejected key is found in the available ones
availableKeys.remove(rejKey)
}
return availableKeys
}

Cannot convert value of type 'String?!' to expected argument type 'Notifications'

I am trying to check the id of a record before I put it into the array, using xcode swift
here is the code. But, i get the following error
Notifications.swift:50:46: Cannot convert value of type 'String?!' to expected argument type 'Notifications'
on this line
*if (readRecordCoreData(result["MessageID"])==false)*
Please can some one help to explain this error
import CoreData
struct Notifications{
var NotifyID = [NSManagedObject]()
let MessageDesc: String
let Messageid: String
init(MessageDesc: String, Messageid:String) {
self.MessageDesc = MessageDesc
self.Messageid = Messageid
// self.MessageDate = MessageDate
}
static func MessagesWithJSON(results: NSArray) -> [Notifications] {
// Create an empty array of Albums to append to from this list
var Notification = [Notifications]()
// Store the results in our table data array
if results.count>0 {
for result in results {
//get fields from json
let Messageid = result["MessageID"] as! String
let MessageDesc = result["MessageDesc"] as? String
let newMessages = Notifications(MessageDesc: MessageDesc!, Messageid:Messageid)
//check with id's from core data
if (readRecordCoreData(result["MessageID"])==false)
{
Notification.append(newMessages)
}
}
}
return Notification
}
//check id
func readRecordCoreData(Jsonid: String) -> Bool {
var idStaus = false
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
//2
let fetchRequest = NSFetchRequest(entityName: "ItemLog")
//3
do {
let resultsCD = try! managedContext.executeFetchRequest(fetchRequest)
if (resultsCD.count > 0) {
for i in 0 ..< resultsCD.count {
let match = resultsCD[i] as! NSManagedObject
let id = match.valueForKey("notificationID") as! String
if (Jsonid as String! == id)
{
idStaus = true
}
else{
idStaus = false
}
}
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return idStaus
}
One of your methods is static and the other one is not :
func readRecordCoreData(Jsonid: String) -> Bool
static func MessagesWithJSON(results: NSArray) -> [Notifications]
Depending on what you want to accomplish you could declare both static, none, or replace
//check with id's from core data
if (readRecordCoreData(result["MessageID"])==false)
{
Notification.append(newMessages)
}
By
//check with id's from core data
if (Notifications.readRecordCoreData(Messageid)==false)
{
Notification.append(newMessages)
}
Not sure if the code will work past compilation however as there are many readability issues