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.
Related
From the documentation I see that I can get some user data (which I'm already getting correctly), however, the way it's structured, it doesn't allow me to access the array outside of it, this is what I mean, I have a function:
func observe() {
let postsRef = Database.database().reference(withPath: "post")
struct test {
static var tempPosts = [Post]()
}
postsRef.observe(.value, with: { snapshot in
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
// let timestamp = data["timestamp"] as? Double,
let first_name = data["Author"] as? String,
let postTitle = data["title"] as? String,
let postDescription = data["description"] as? String,
let postUrl = data["postUrl"] as? String,
let postAddress = data["Address"] as? String,
let url = URL(string:postUrl)
{
// Convert timestamp to date
// let newDate = self.getDateFromTimeStamp(timestamp:timestamp)
// Store variables from DB into post
let post = Post(author: first_name, postTitle: postTitle, postDescription: postDescription, postUrl: url, postAddress: postAddress)
test.tempPosts.append(post)
}
}
self.posts = test.tempPosts
// HERE IT WORKS
print(test.tempPosts[0].postTitle , " 0")
self.tableView.reloadData()
})
// HERE IT DOESN'T WORK
print(test.tempPosts[0].postTitle , " 0")
}
and I'm trying to access the data where it says: // HERE IT DOESN'T WORK, how can I access that array outside of it? I need to call it later
The observe() method is asynchronous, so after you call postsRef.observe the code executed within that closure is run ONLY AFTER the application receives a response from Firebase, so there's a delay. All code after this call that's NOT stored within the closure will be executed immediately though.
So the .observe asynchronous function call is executed, and then the next line under // HERE IT DOESN'T WORK is executed immediately after. This is why this doesn't work because test.tempPosts doesn't contain any values until after the server response is received, and by that time, your print statement outside the closure has already run.
Check out this StackOverflow answer to get some more information about async vs sync.
Asynchronous vs synchronous execution, what does it really mean?
Also too, you may want to look into closures on Swift here.
If you want to access the value outside of the closure, you'll need to look into using a completion handler or a class property.
Edit:
Here's an example
func observe (finished: #escaping ([Post]) -> Void) {
// ALL YOUR CODE...
finished(test.tempPosts)
}
func getTempPosts () {
observe( (tempPosts) in
print(tempPosts)
}
}
I'm having some trouble with an array. I created an array called 'coins'
var coins = [Coin]()
then appended objects to it within a function
func getCoinData() {
AF.request("https://min-api.cryptocompare.com/data/top/mktcapfull?limit=10&tsym=USD", encoding: JSONEncoding.default).responseJSON { response in
if let json = response.result.value{
let responseDictionary = json as! [String : Any]
let data = responseDictionary["Data"] as! [Any]
for index in data {
let coin = index as! Dictionary<String, Any>
let coinInfo = coin["CoinInfo"] as! Dictionary<String, Any>
let displayInfo = coin["DISPLAY"] as! Dictionary<String, Any>
let usdDisplayInfo = displayInfo["USD"] as! Dictionary<String, Any>
let name = coinInfo["Name"]
let fullName = coinInfo["FullName"]
let imageUrl = coinInfo["ImageUrl"]
let price = usdDisplayInfo["PRICE"]
let marketCap = usdDisplayInfo["MKTCAP"]
let change24Hr = usdDisplayInfo["CHANGE24HOUR"]
let newCoin = Coin()
if let newCoinName = name, let newCoinFullName = fullName, let newCoinImageUrl = imageUrl, let newCoinPrice = price, let newCoinMarketCap = marketCap, let newCoinChange24hr = change24Hr {
let coinName = newCoinName
let coinFullName = newCoinFullName
let coinImageUrl = newCoinImageUrl
let coinPrice = newCoinPrice
let coinMarketCap = newCoinMarketCap
let coinChange24Hr = newCoinChange24hr
newCoin.name = "\(coinName)"
newCoin.fullName = "\(coinFullName)"
newCoin.imageURL = "\(coinImageUrl)"
newCoin.price = "\(coinPrice)"
newCoin.marketCap = "\(coinMarketCap)"
newCoin.change24Hr = "\(coinChange24Hr)"
self.coins.append(newCoin)
}
}
}
}
}
When i print 'self.coins.count' within the scope of the function i can see the count incrementing. Outside the function it's reading 0 items in the array.
Written for Swift 5
The problem is that you have a URL request which is Asynchronous. This means that the task is not waited for to complete.
In your problem, inside the function coins is printed after it has been assigned, after the URL request. However, when coins is printed outside the function, it is printed before it has been changed, as the URL request has not yet completed.
To solve this, you need to create a completion handler. A basic one is shown here:
// Our errors which could occur
enum SomeError: Error { case unknown }
// Function which is ASYNCHRONOUS
func someAsyncFunction(completion: #escaping (Result<Int, SomeError>) -> ()) {
// Temporary for this example
let success = true
let myNum = 3
// Return value if it is a success, otherwise return the error
if success {
completion(.success(myNum))
} else {
completion(.failure(.unknown))
}
}
// Call
someAsyncFunction { (result) in
print("Result: \(result)")
/* PRINT COINS HERE */
}
See a full guide on completion handlers using Result in Swift 5 at hackingwithswift.com.
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
}
I have an issue I'm trying to figure out. I'm not sure what an "elegant" way to approach this would be. This is my first Swift project, so excuse me if I'm asking something ridiculous.
I have this controller/image picker to get me the latitude/longitude of a photo in a user's photo library.
//Get metadata from a photo. Save location (latitude/longitude)
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let picker = UIImagePickerController()
picker.delegate = self // delegate added
if let URL = info[UIImagePickerControllerReferenceURL] as? URL {
print("We got the URL as \(URL)")
let opts = PHFetchOptions()
opts.fetchLimit = 1
let assets = PHAsset.fetchAssets(withALAssetURLs: [URL], options: opts)
for assetIndex in 0..<assets.count {
var asset = assets[assetIndex]
var location = String(describing: asset.location!)
var photo_latitude = asset.location?.coordinate.latitude
var photo_longitude = asset.location?.coordinate.longitude
var coords : [String: Double] = ["longitude": photo_longitude!, "latitude": photo_latitude!]
PHAsset.fetchAssets(withALAssetURLs: [URL], options: nil)
dismiss(animated:true, completion: nil)
PHImageManager.default()
.requestImageData(for: asset,
options: nil) { resultData, response, orientation, info in
var data = resultData
DropboxClientsManager
.authorizedClient?
.files
.upload(path: "/development/image.jpg", mode: .overwrite, input : data!)
print(data!)
}
}
}
}
}
I'm also uploading the image they select to Dropbox.
Here's my issue. I want to get the address from the latitude and longitude, but I need some way to pass the latitude and longitude into the appropriate function. I'm using the following code with the Google Maps API to find an address from latitude and longitude.
let baseUrl = "http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true/false"
let apikey = "hiding_my_api_key"
func getAddressFromGoogle() {
Alamofire.request(baseUrl, method: .get).responseJSON {
response in
if response.result.isSuccess {
print("Successful request.")
var locationData = JSON(response.result.value!)
let streetAddress = locationData["results", 0, "formatted_address"]
print(streetAddress)
print(locationData)
}
else {
print("Error \(response.result.error)")
}
}
}
What would be the best way for me to pass photo_longitude and photo_latitude to my getAddressFromGoogle function?
Thanks in advance.
The typical Swift way to approach this would be to attack it head on. Latitude and longitude are both floating point numbers, and you would usually pass them as such:
func getAddressFrom(lat: Double, long: Double)
Once inside the getAddressFrom(_:_:) method, you would typically use an instance of number formatter to convert each Double to a String of the format Google’s API expects:
func getAddressFrom(lat: Double, long: Double) {
let baseUrl = "http://maps.googleapis.com/maps/api/geocode/json?", urlSuffix = "&sensor=true/false"
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 6
// Do other configuration to get the number formatter to produce the output you need…
let numberLat = NSNumber(floatLiteral: lat), numberLong = NSNumber(floatLiteral: long)
guard let textLat = numberFormatter.string(from: numberLat),
let textLong = numberFormatter.string(from: numberLong) else {
// Handle invalid latitude or longitude…
return
}
let completeUrl = baseUrl + textLat + "," + textLong + urlSuffix
// Remaining body of function…
}
In production, you would probably configure the number formatter in the class body to reuse for many images.
Looking at your image picker controller, I see the latitude and longitude you are pulling off the images are optional values, since they may or may not be nil. When calling getAddressFrom(_:_:), you would probably use optional binding to unwrap the optionals and handle the nil case:
guard let photo_latitude = photo_latitude, let photo_longitude = photo_longitude else {
// Handle a photo missing latitude, longitude, or both…
return
}
getAddressFrom(lat: photo_latitude, long: photo_longitude)
Note that it is uncommon to use underscores in variable names in Swift. photoLatitude and photoLongitude are more Swift-y.
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.