In my ViewController I have a label which displays the name of a club selected in the previous ViewController. I now need to create a query from my parse.com class to retrieve all information on the object that matches the club name displayed as a label. I know how to successfully query parse but I'm finding it hard to find a way to match the query with the label String seeing as the label String can be different depending on what club was selected in the previous view.
Code:
import UIKit
import Parse
class MenuController: UIViewController {
#IBOutlet weak var clubLabel: UILabel!
var clubName = String()
override func viewDidLoad() {
super.viewDidLoad()
clubLabel.text = clubName
}
}
Previous View already queried parse to populate map with club annotations like so:
let annotationQuery = PFQuery(className: "Clubs")
annotationQuery.findObjectsInBackgroundWithBlock{
(clubs, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
// Do something with the found objects
let myClubs = clubs! as [PFObject]
for club in myClubs {
//data for annotation
let annotation = MKPointAnnotation()
let place = club["location"] as? PFGeoPoint
let clubName = club["clubName"] as? String
let stadiumName = club["stadium"] as? String
annotation.title = clubName
annotation.subtitle = stadiumName
annotation.coordinate = CLLocationCoordinate2DMake(place!.latitude,place!.longitude)
//add annotations
self.mapView.addAnnotation(annotation)
Instead of using default MKPointAnnotation, we will use custom annotations for easier understanding.
First we will create a class ClubAnnotation conforming to the MKAnnotation protocol in a different file.
import UIKit
import MapKit
import Parse
class ClubAnnotation: NSObject, MKAnnotation {
//MARK: - Instance properties
let club:PFObject
//MARK: - Init methods
init(club:PFObject) {
self.club = club
}
//MARK: - MKAnnotation protocol conformance
var title: String? { get {
return club["clubName"]
}
}
var subtitle: String? { get {
club["stadium"]
}
}
var coordinate: CLLocationCoordinate2D { get {
return CLLocationCoordinate2D(latitude: club["location"].latitude, longitude: club["location"].longitude)
}
}
}
I let you handle the error if the fields "stadium", "clubName" or "location" are empty for a Club object, so you can decide how do display it.
Then, in your class including the MKMapView object (and probably conforming to MKMapViewDelegate protocol), replace the handling of your query result :
let annotationQuery = PFQuery(className: "Clubs")
annotationQuery.findObjectsInBackgroundWithBlock{
(clubs, error) -> Void in
if let clubs = clubs where error == nil {
// The find succeeded.
print("Successful query for annotations")
for club in clubs {
self.mapView.addAnnotation(ClubAnnotation(club:club))
}
} else {
// Handle the error
}
}
You realize that i don't have to manually set title, subtitle and coordinate properties since your ClubAnnotation object returns it directly according to it's implementation of MKAnnotation protocol.
Still in this class, add a selectedClub property. This property will be used to save the club from the annotation selected and will be passed to the pushed view controller.
var selectedClub:PFobject!
In your calloutAccessoryControlTapped delegate method, you can then get your PFObject from the annotation, and pass it to a new viewController of do everything you want with it, without having to query it from Parse :
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
mapView.deselectAnnotation(view.annotation, animated: true)
if let annotation = view.annotation as? ClubAnnotation {
clubSelected = annotation.club
}
}
You now have saved the club object from your custom annotation class, and you have a reference to it. If you want to present or push a new view controller with more information about this club, just override prepareForSegue and set this value to the pushed/presented view controller. You don't need to query other fields of your club since they are all loaded according to your Parse request.
Simply add a whereKey to your PFQuery.
annotationQuery.whereKey(clubName, equalTo: "clubName")
Hope this helps.
Related
I have a custom NSMutableArray and i want to sort it; I've looked over the internet but nothing solve my problem. what I have is the following Locations.swift
import UIKit
class Location: NSObject {
var id:String = String()
var name:String = String()
var distance:Float = Float()
}
then I create a Mutable Array from this class in my ViewController.swift
class CustomViewController: UIViewController{
var locations: NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
locations = NSMutableArray()
locations = SqLite.getInstnace().getLocations()//get values from DB، objects of Location
}
}
how I can sort locations by the distance value??
I would make location an array of Location objects, and then sorting becomes easy with sortInPlace.
class CustomViewController: UIViewController{
var locations = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
locations = SqLite.getInstnace().getLocations().flatMap { $0 as? Location }
}
}
Now you can sort locations like this:
locations.sortInPlace { $0.distance < $1.distance }
You could always use the built in sort function:
let locations = SqLite.getInstnace().getLocations().sort({
return ($0 as! Location).distance < ($1 as! Location).distance
})
if SqLite.getInstnace().getLocations() return an array as [Location] and not a NSArray then you could just do this since it will already know the elements types:
let locations = SqLite.getInstnace().getLocations().sort({
return $0.distance < $1.distance
})
This should return a sorted array in ascending order if you want descending just use > instead.
Also, there is really no need to use NSMutableArrays in Swift. Just use [Location].
Edit:
NSMutableArray can be used in swift but it considered objective-c like where as using [OBJECT_TYPE] is conventional for swift.
Take a look at the Swift 2 book array segment. None of the examples use NSArray or NSMutable array.
By declaring something with var in swift makes it mutable where as let makes something immutable
import UIKit
import Parse
class loadMainViewController: UIViewController {
//create new pfQuery - This is the bridge between our app and Parse: "trivia" is our class name on Parse
let queryTrivia: PFQuery = PFQuery(className:"trivia")
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//retrieve data from parse query
retrieveTrivia()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func retrieveTrivia() {
//This CLOSURE gives access to all objects in "trivia" class using our queryTrivia Bridge
queryTrivia.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error:NSError?) -> Void in
if( error == nil ){
print("error is nil")
}
var didLoad = true
// Loop through the objects array
for triviaObject in objects!{
// Retrieve data for each object (key, question, ans2, ans3, correctAns)
let triviaQuest : String? = (triviaObject as PFObject)["question"] as? String
let triviaAns2 : String? = (triviaObject as PFObject)["ans2"] as? String
let triviaAns3 : String? = (triviaObject as PFObject)["ans3"] as? String
let triviaAns : String? = (triviaObject as PFObject)["correctAns"] as? String
let triviaKey : Int? = (triviaObject as PFObject)["key"] as? Int
//Check that items are not nil, and create trivia object, add to triviaQuestions Array
if ( triviaKey != nil && triviaQuest != nil && triviaAns2 != nil && triviaAns3 != nil && triviaAns != nil){
let trivia = triviaQuestion(Key: triviaKey!, Question: triviaQuest!, Answer: triviaAns!, WrongAnswer: triviaAns2!, WrongAnswer2: triviaAns3!)
triviaQuestions.append(trivia) // append to the global array of trivia questions
}else{
self.label1.text = "Network Error"
didLoad = false
}
}
for element in triviaQuestions{
print(element.Key)
}
if (didLoad == true) {
//perform segue to View Controller : Main menu
self.performSegueWithIdentifier("finnishLoad", sender: self)
}
}
}
}
My problem Lies when I query the data in the retrieveTrivia() method. The queries apeare to work with a few Problems. The data being saved in to the TriviaQuestion, TriviaAns, TriviaAns2, TriviaAns3 is old data that I have since changed using The parse website by simply clicking in the cell and editing each feild by hand. Furthermore I cannot seem to get the key field and it is always coming back as nil.
When I run this the triviaAns variable contains "Cow" but, online when looking at the data it says "Mozzarella."
Any idea as to why I would be receiving the wrong data after updating the fields and why the key field is coming back as nil?
Here is what the data looks like now
enter image description here
Make sure that you are connecting to the correct Parse app - Check the keys in the call to setApplicationId:clientKey in your App Delegate match those shown in your app's settings on Parse.com
I have a map that allows me to add pins (annotations) to it. When I click on a pin, the app goes to another view controller and shows images downloaded from online. I "save" these images to an array but later I cannot access them. For example, when I tap "Back" to go to the previous view controller and tap on the same pin from before, instead of showing the images I originally downloaded and saved, new images are downloaded from online. Essentially, the images in the array are replaced. How can I save the images and retrieve them? Sorry for long amounts of code, I shortened as much as I could.
This is my class for the images:
class Image {
var image: UIImage
init(image: UIImage) {
self.image = image
}
}
This is my class for the pins. Notice, an array of type Image from the Image class is in here:
class Pin: Hashable {
var hashValue: Int {
get {
return "\(latitude.hashValue),\(longitude.hashValue)".hashValue
}
}
let latitude: Double
let longitude: Double
var images = Array<Image>()
init(latitude: Double, longitude: Double)
{
self.latitude = latitude
self.longitude = longitude
}
}
// must be declared in the global scope! and not just in the class scope
func ==(lhs: Pin, rhs: Pin) -> Bool
{
return lhs.hashValue == rhs.hashValue
}
I add pins to a Set like so (no duplicates of pins allowed). Also, the selected pin is sent to the secondViewController:
class MapViewController: UIViewController, MKMapViewDelegate, UIGestureRecognizerDelegate {
var pins = Set<Pin>()
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
// Add pin to set
let selectedCoordinatePoint = Pin(latitude: latFromPin, longitude: lonFromPin)
var select = "\(latFromPin.hashValue),\(lonFromPin.hashValue)".hashValue
pins.insert(selectedCoordinatePoint)
//Goto to next view controller and show data depending on the pin selected
for pin in pins {
if pin.hashValue == select.hashValue {
dispatch_async(dispatch_get_main_queue()) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = self.storyboard!.instantiateViewControllerWithIdentifier("CollectionViewControllerID") as! CollectionViewController
secondViewController.pin = pin
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
}
}
}
I download images from online and append to an array on this secondViewController (I shortened the code here):
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, MKMapViewDelegate {
var pin: Pin!
override func viewWillAppear(animated: Bool) {
if let photosArray = photosDictionary["photo"] as? [[String:AnyObject]] {
// println("photosArray = \(photosArray )")
var count = 0
for photo in photosArray {
// 6 - Grab 21 random images
if count <= 20 {
// Grabs 1 image
let randomPhotoIndex = Int(arc4random_uniform(UInt32(photosArray.count)))
let photoDictionary = photosArray[randomPhotoIndex] as [String:AnyObject]
// 7 - Get the image url
let imageUrlString = photoDictionary["url_m"] as? String
let imageURL = NSURL(string: imageUrlString!)
// 8 - If an image exists at the url, append to array
let imageData = NSData(contentsOfURL: imageURL!)
let finalImage = UIImage(data: imageData!)
var image = Image(image: finalImage!)
self.pin.images.append(image)
count += 1
println(self.pin.images.count)
}
}
}
}
}
I'm not I understand what your asking exactly...you are making network calls to populate your image array. If you just want to pass the image array from VC to VC, you can use prepareForSegue to pass the value.
If you want to pull these down and then store them locally you need to read up on persistence. For example you can use something like Core Data, Parse, Realm...I personally use Parse because of it's ease. This is a good tutorial along the lines of what you're doing I think. It uses Parse to store the image data: http://www.appcoda.com/instagram-app-parse-swift/
I want to create an object graph of Pins with Images inside of the Pins. I have a class Pin, which stores map annotations (pins). Inside this class, I have an array of images of type Image. My goal is to have each and every pin have it's own set of images based on the coordinate points of that pin. (This is not about Core Data. It is about implementing the Image class below).
Basically, I want to create a Class Image to put the downloaded images there and store it for the selected pin. Once I have the 21 images for the pin, I do not want to re-download new images for that specific pin unless a button is pressed to do so. For example, if a pin is selected for Times Square, New York and I get 21 images of dogs on the street, this will be saved. If I now go back to the map select a different pin and then decide to reselect the same Times Square, New York from before, I should still see the same 21 images of dogs on the street. Only by clicking a button for re-downlaoding will new pictures replace the 21 images of dogs on the street to who knows what is on Flickr for those coordinates.
import Foundation
import UIKit
import MapKit
class Pin: Hashable {
var hashValue: Int {
get {
return "\(latitude.hashValue),\(longitude.hashValue)".hashValue
}
}
let latitude: Double
let longitude: Double
var images = [Image]()
init(latitude: Double, longitude: Double)
{
self.latitude = latitude
self.longitude = longitude
}
}
// must be declared in the global scope! and not just in the class scope
func ==(lhs: Pin, rhs: Pin) -> Bool
{
return lhs.hashValue == rhs.hashValue
}
My Image class is currently empty:
import UIKit
class Image {
}
When a user drops a pin on the map using a long press gesture, this pin is not added to the Set of pins. This happens only when a user selects the pin. See this code:
class MapViewController: UIViewController, MKMapViewDelegate, UIGestureRecognizerDelegate {
var pins = Set<Pin>()
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
if editAndDoneButtonText.title == "Edit" {
println("Pin Selected")
/* Get lat/long coordinates of a pin by tapping on the it and update appDelegate variables.
Will use this for networking call to get Flickr images and to populate data structure for storage.
*/
var latFromPin = view.annotation.coordinate.latitude
var latString = String(stringInterpolationSegment: latFromPin)
appDelegate.LAT = latString
var lonFromPin = view.annotation.coordinate.longitude
var lonString = String(stringInterpolationSegment: lonFromPin)
appDelegate.LON = lonString
latCollectionView = latFromPin
lonCollectionView = lonFromPin
// Add pin to set
let selectedCoordinatePoint = Pin(latitude: latFromPin, longitude: lonFromPin)
var select = "\(latFromPin.hashValue),\(lonFromPin.hashValue)".hashValue
pins.insert(selectedCoordinatePoint)
// Goto to next view controller and show data depending on the pin selected
for pin in pins {
if pin.hashValue == select.hashValue {
println("SAME: pin.hashValue: \(pin.hashValue), select.hashValue: \(select.hashValue)")
dispatch_async(dispatch_get_main_queue()) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = self.storyboard!.instantiateViewControllerWithIdentifier("CollectionViewControllerID") as! CollectionViewController
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
}
}
// Remove pins from map by tapping on it
if editAndDoneButtonText.title == "Done" {
var annotationToRemove = view.annotation
mapView.removeAnnotation(annotationToRemove)
println("remove pin - didSelectAnnotationView")
}
}
When a pin is selected, the app goes to the next view controller. Inside viewWillAppear, a networking call is made to Flickr to download images that are associated with the lat/lon coordinates of the selected pin. These images are used to be displayed in a collection view.
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, MKMapViewDelegate {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var arrayOfImages: [UIImage] = [] // Array from Flickr
var pin: Pin!
override func viewWillAppear(animated: Bool) {
// Flickr
// 2 - API Method Arguments
let methodArguments = [
"method": appDelegate.METHOD_NAME,
"api_key": appDelegate.API_KEY,
"extras": appDelegate.EXTRAS,
"lat": appDelegate.LAT,
"lon": appDelegate.LON,
"format": appDelegate.DATA_FORMAT,
"nojsoncallback": appDelegate.NO_JSON_CALLBACK
]
// 3 - Initialize session and url
let session = NSURLSession.sharedSession()
let urlString = appDelegate.BASE_URL + appDelegate.escapedParameters(methodArguments)
let url = NSURL(string: urlString)!
let request = NSURLRequest(URL: url)
// 4 - Initialize task for getting data
let task = session.dataTaskWithRequest(request) { data, response, downloadError in
if let error = downloadError {
println("Could not complete the request \(error)")
} else {
// 5 - Success! Parse the data
var parsingError: NSError? = nil
let parsedResult: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &parsingError) as! NSDictionary
// println("parsedResult = \(parsedResult)")
if let photosDictionary = parsedResult.valueForKey("photos") as? [String:AnyObject] {
// println("photosDictionary = \(photosDictionary)")
if let photosArray = photosDictionary["photo"] as? [[String:AnyObject]] {
// println("photosArray = \(photosArray )")
var count = 0
for photo in photosArray {
// 6 - Grab 21 random images
if count <= 20 {
// Grabs 1 image
let randomPhotoIndex = Int(arc4random_uniform(UInt32(photosArray.count)))
let photoDictionary = photosArray[randomPhotoIndex] as [String:AnyObject]
// 7 - Get the image url
let imageUrlString = photoDictionary["url_m"] as? String
let imageURL = NSURL(string: imageUrlString!)
// 8 - If an image exists at the url, append to array
let imageData = NSData(contentsOfURL: imageURL!)
let finalImage = UIImage(data: imageData!)
self.arrayOfImages.append(finalImage!) // Append to array outside of this closure
count += 1
println(self.arrayOfImages.count)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
}
}
}
}
}
// 6 - Resume (execute) the task
task.resume()
}
// Collection view......
}
Currently I am using an array called var arrayOfImages: [UIImage] = []. The networking call downloads 21 images (if available) from Flickr for each pin and stores it in this array.
I've been able to store the pins. Can anyone help with this?
Not sure what the question is... but seems like you're storing a collection of Images correctly. All that's left is to implement the Image class. You can create a property within it called photo that has the class UIImage then instantiate those in your network call. Is that what you were looking for?
In Swift I have this object City:
class City {
var name:String
init(name: String) {
self.name = name
}
var file:String?
var description:String?
}
now in my viewcontroller I fill an array with these type of objects and I want to fill a tableview, but I'm not able to access at the property of this object.
I show you the code, that doesn't take the property "file":
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = menu_list.dequeueReusableCellWithIdentifier("list_cell") as ListCell
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
var obj = list_element.objectAtIndex(indexPath.row)
var name = obj.file
return cell
}
Now STOP! You can suggest me to add this "as City"
var obj = list_element.objectAtIndex(indexPath.row) as City
but I don't know what type of objects is filled my array, I have other object as Country and State, then what's the way to have a general managing of objects? In obj-c I did it with "id" but in swift?
In Swift, you can use an NSArray if you like, you just have to make sure all of your objects are subclasses of NSObject. Swift also allows you to declare an array of type [Any] which can hold any type. You then use conditional cast as? to work with the objects:
class City {
}
class Country {
}
class State {
}
var places = [Any]()
places.append(City())
places.append(State())
places.append(Country())
places.append(Country())
places.append(13)
for place in places {
if let loc = place as? City {
println("processing city")
}
if let loc = place as? State {
println("processing state")
}
if let loc = place as? Country {
println("processing country")
}
}
You can also use a switch to determine the type:
for place in places {
switch place {
case let loc as City:
println("I see a city")
case let loc as State:
println("I see a state")
case let loc as Country:
println("I see a country")
default:
println("I don't know what it is")
}
}
If all you are putting in your array of places is City, State, and Country objects, it would be better form to have those three classes derive from a base class (let's call it Location) and then have your array of places be of type [Location].