I am trying to implement loading data from Parse server after tapping a button on postVC to load data and navigate to guestVC. It was working fine and at some point began to crash the app...not sure why? I am getting the fatal error: unexpectedly found nil while unwrapping an Optional value...Any and all direction or help would be greatly appreciated. Thanks!
import UIKit
import Parse
var postuuid = [String]()
class postVC: UITableViewController {
//postVC button click function
//clicked username button from post
#IBAction func usernameBtn_click(sender: AnyObject) {
let i = sender.layer.valueForKey("index") as! NSIndexPath
let cell = tableView.cellForRowAtIndexPath(i) as! postCell
// if user tapped on himself go home, else go guest
if cell.usernameBtn.titleLabel?.text == PFUser.currentUser()?.username {
let home = self.storyboard?.instantiateViewControllerWithIdentifier("homeVC") as! homeVC
self.navigationController?.pushViewController(home, animated: true)
} else {
let guest = self.storyboard?.instantiateViewControllerWithIdentifier("guestVC") as! guestVC
self.navigationController?.pushViewController(guest, animated: true)
}
}
// guestVC relevant code
import UIKit
import Parse
var guestname = [String]()
class guestVC: UICollectionViewController {
var uuidArray = [String]()
var picArray = [PFFile]()
// posts loading function
func loadPosts() {
let query = PFQuery(className: "posts")
// app keeps crashing in line below when I try to load data to guestVC
query.whereKey("username", equalTo: guestname.last!)
query.limit = self.page
query.findObjectsInBackgroundWithBlock( { (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.uuidArray.removeAll(keepCapacity: false)
self.picArray.removeAll(keepCapacity: false)
for object in objects! {
self.uuidArray.append(object.valueForKey("uuid") as! String)
self.picArray.append(object.valueForKey("pic") as! PFFile)
}
self.collectionView?.reloadData()
} else {
print(error!.localizedDescription)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// code here will be executed as the main queue
})
})
}
You use a lot of exclamation marks to force unwrap optional values in your code, it's a bad habit.
For example, you can unwrap guestname.last safely by:
guard let lastItem = guestname.last else {
// do something else
return
}
query.whereKey("username", equalTo: lastItem)
Before adding or appending, check dictionary key has valid value or not. Check if 'uuid' or 'pic' key has value in dictionary or not. If it has then add/append.
Related
I am importing data from Parse and trying to put it into a page view controller. The code for the query is correct, but it is not being called. It tries to add the objects inside the array to the pageviewcontroller, but those arrays are empty, they will only get populated after the query. So how do I make sure the query runs and populates the array first?
override func viewDidLoad() {
super.viewDidLoad()
testRetrieveInfo()
}
func retrieveinfo(callback:([String])->Void){
let query = PFQuery(className: "Animals")
query.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error: NSError?) in
if(error == nil){
for object in objects!{
if let importname = object["Name"] as? String{
self.name = NSArray(object: importname)
print(self.name) //Nothing prints, I also breakpoint and it crashes first, crash location is above in viewDidLoad
}
}
}else{
print(error)
}
}
}
}
func testRetrieveInfo(){
self.retrieveInfo { (results:[String]) -> Void in
//Setup here the PageViewController
self.pageViewController.storyboard?.instantiateViewControllerWithIdentifier("PageVC") as? UIPageViewController
self.pageViewController.dataSource = self
var startVC = self.viewControllerAtIndex(0) as? Jwasy
var viewControllers = NSArray(object: startVC!)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward , animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 30, self.view.frame.width, self.view.frame.height)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
print("called")//not printed
}
}
To be informed when you download the data, you have different options (but the most basic it's set up the PageViewController inside the callback of the query):
0 - You can create a method that set the PageViewController and called inside your callback of the query.
1 - Create your own callback
func retrieveInfo(callback:([String])->Void){
//Inside your query callback you're going to call yours
let query = PFQuery(className: "Animals")
query.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error: NSError?) in
if(error == nil){
var results:[String] = []
for object in objects!{
if let importname = object["Name"] as? String{
self.results.append(importname)
}
}
callback(results)
}else{
//Can also create a callback for failure
print(error)
}
}
}
//Used
func testRetrieveInfo(){
self.retrieveInfo { (results:[String]) -> Void in
//Setup here the PageViewController
}
}
2 - Creating a Delegate
3 - Notification Center
Note:
You're recreating the array of names every time inside the loop. Can you take a print screen of the breakpoint?
I am trying to create an array of strings for all the usernames using the following code and populate a TableViewController.
class TableViewController: UITableViewController {
var randomUser = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var query: PFQuery = PFUser.query()!
query.findObjectsInBackgroundWithBlock {(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil{
if let objects = (objects as? [PFObject]!){
for object in objects{
self.randomUser.append(object.objectForKey("username") as! String)
print(object.objectForKey("username") as! String)
print(self.randomUser.count)
}
}
}
}
print(self.randomUser.count)
}
the output in the console:
0
username
1
username
2
username
3
But UItableview does not populate.. What could be causing this?
My guess is that query is delayed and view is created before it can return data. Thank you for any help!
Yes, you are right. You need to call self.tableView.reloadData() after you get the results of the query. Below is an example of where to call it.
private var usersArray = [PFUser]()
func fetchUsers() {
let userQuery: PFQuery = PFUser.query()!
userQuery.orderByAscending("username")
userQuery.whereKey("username", notEqualTo: (currentUser?.username)!)
userQuery.findObjectsInBackgroundWithBlock({
(users, error) -> Void in
if error == nil {
self.usersArray = users as! [PFUser]
self.tableView.reloadData()
} else {
print(error)
}
})
}
In this example, you can then access the username property by doing usersArray[i].username
I'm a beginner working with Parse and Swift. I need to update the object referred to in my viewDidLoad in another function within the same controller. How do I pass the currently loaded object's objectId without having to hardcode it like this:
query.getObjectInBackgroundWithId("8DkYgraEJq")
Here is my viewDidLoad function:
override func viewDidLoad() {
var query = PFQuery(className: "CheckedBaggage")
query.orderByAscending("createdAt")
query.whereKey("respondedTo", notEqualTo: true)
query.getFirstObjectInBackgroundWithBlock {
(CheckedBaggage: PFObject!, error: NSError!) -> Void in
if error != nil {
println("The getFirstObject request failed.")
} else {
// The find succeeded.
self.randomBaggageLabel.text = CheckedBaggage.objectForKey("message") as? NSString
CheckedBaggage.save()
println(CheckedBaggage.objectId)
let baggageId = CheckedBaggage.objectId
println("Successfully retrieved the object.")
}
}
I would like to try and pass the variable baggageId, which should be the object's ID as a string, as an argument to the getObjectInBackgroundWithId block in my carryIt function:
#IBAction func carryIt(sender: AnyObject!) {
println("CarryIt is being called")
var query = PFQuery(className: "CheckedBaggage")
query.getObjectInBackgroundWithId(baggageId) {
(CheckedBaggage: PFObject?, error: NSError?) -> Void in
if error != nil {
println(error)
} else if let CheckedBaggage = CheckedBaggage {
println("object hello!")
CheckedBaggage["respondedTo"] = true
CheckedBaggage["response"] = self.kindnessMessage.text
CheckedBaggage.save()
}
}
}
But I'm getting an "unresolved identifier" error. It updates my Parse database perfectly fine if I hardcode the object ID, but I can't do it this way. Here's a screenshot of the error:
Thank you so much for your help!
You have to initialize baggageId. To use it in multiple functions, it must be scoped at class level as the comment said. To set it after it has been declared, it must be a "var", not a constant "let".
var baggageId = ""
func viewDidload() {
var query = ...
query.get... {
baggageId = CheckedBaggege.objectId
}
}
func shipIt() {
var query = ...
query.getObjectWithId(baggageId) ...
}
I am trying to add the retrieved object from Parse to an array. It has found the user, it is printed in my logs. All the key names match up, I can't seem to find why both my userArray and imageFiles array are printed to the logs as empty. Thanks for the help!
var userArray: [String] = []
var refresher: UIRefreshControl!
var imageFiles = [PFFile]()
override func viewDidLoad() {
super.viewDidLoad()
PFGeoPoint.geoPointForCurrentLocationInBackground { (geopoint: PFGeoPoint!, error: NSError!) -> Void in
if error == nil {
println(geopoint)
var user = PFUser.currentUser()
user["location"] = geopoint
user.save()
var query = PFUser.query()
query.whereKey("location", nearGeoPoint:geopoint)
query.limit = 10
query.findObjectsInBackgroundWithBlock({ (users: [AnyObject]!, error: NSError!) -> Void in
for user in users {
self.userArray.append(user["name"] as! String)
self.imageFiles.append(user["profilePicFile"] as! PFFile)
println(user)
}
})
}
}
self.refresher = UIRefreshControl()
self.refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(self.refresher)
//follow udemy to get pull to refresh , need update users
println(userArray)
println(imageFiles.count)
}
func updateUsers() {
self.userArray.removeAll(keepCapacity: true)
self.imageFiles.removeAll(keepCapacity: true)
var aquery = PFUser.query()
aquery.whereKey("username", equalTo: PFUser.currentUser().username)
var cools = aquery.findObjects()
var query = PFUser.query()
query.whereKey("location", nearGeoPoint: cools[0]["location"] as! PFGeoPoint!)
query.limit = 10
query.findObjectsInBackgroundWithBlock({ (users: [AnyObject]!, error: NSError!) -> Void in
for user in users {
self.userArray.append(user["name"] as! String)
self.imageFiles.append(user["profilePicFile"] as! PFFile)
}
self.tableView.reloadData()
self.refresher.endRefreshing()
})
}
The reason is because PFGeoPoint.geoPointForCurrentLocationInBackground is an asynchronous method that takes a callback closure as a parameter to fire off as soon as the asynchronous work is done. So viewDidLoad will call that method but continue on and print userArray before the geoPointForCurrentLocationInBackground callback gets fired.
This is a big feature of functional programming languages so I suggest reading up on closures when you can.
http://code.tutsplus.com/tutorials/swift-from-scratch-closures--cms-23138
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
If you put the println statements after you iterate the users inside of the callback, you will see your data:
for user in users {
self.userArray.append(user["name"] as! String)
self.imageFiles.append(user["profilePicFile"] as! PFFile)
println(user)
}
}
println(userArray) // you will see userArray is populated
println(imageFiles.count)
I keep getting the following error :
fatal error: unexpectedly found nil while unwrapping an Optional value
and I guess I just don't understand why. Can someone please help me find my mistake? Is it that variable results is optional?
The error keeps pointing to a line in viewDidLoad(), I commented where. Thanks.
//
// ViewController.swift
// Physics Help!
//
// Created by Sam Hanson on 2/8/15.
// Copyright (c) 2015 Sam Hanson. All rights reserved.
//
import UIKit
import CloudKit
class ViewController: UIViewController {
//VARIABLES********************************************************
#IBOutlet var c1Answer: UILabel!
#IBOutlet var questions: UILabel!
var resultsOfDB : String = ""
var indexes : [Int] = []
var counter : Int = 0
var newStr : String = ""
//*****************************************************************
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.questions.text = String(getNewQbutton()) //error points to here*********
}
//load the answers, grab them from the cloud base
#IBAction func loadAnswers() {
let container = CKContainer.defaultContainer()
var publicDB = container.publicCloudDatabase
let myQuery = CKQuery(recordType: "QuestionsTable", predicate: NSPredicate(value: true))
publicDB.performQuery(myQuery, inZoneWithID: nil){
results, error in
if error != nil {
println(error)
}
else
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.c1Answer.text = results.description
println(results.description)
})
}
}
}
#IBAction func getNewQbutton() {
let container = CKContainer.defaultContainer()
var publicDB = container.publicCloudDatabase
let myQuery = CKQuery(recordType: "QuestionsTable", predicate: NSPredicate(value: true))
publicDB.performQuery(myQuery, inZoneWithID: nil){
results, error in
if error != nil {
println(error)
}
else
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.resultsOfDB = results.description
//for each character in resultsOfDB
for character in self.resultsOfDB{
if(character == "\""){
self.indexes.append(self.counter)
}
self.counter++
}
self.newStr = self.resultsOfDB.substringWithRange(Range<String.Index>(start: advance(self.resultsOfDB.startIndex, self.indexes[0] + 1), end: advance(self.resultsOfDB.endIndex, -(self.counter - self.indexes[1]))))
self.questions.text = self.newStr
})
}
self.counter = 0
}
}
There can be two reasons for this problem:
1.
This can mean that you are trying to call a function (text?) of an object (questions?) which is not initialized.
My guess is that questions is not initialized. So, when your call questions.text, you are calling text function on a nil outlet.
Make sure that your outlets questions are hooked up properly in the storyboard (you should see a circle near your #IBOutlet). Also, make sure you haven't set up multiple connections to your outlet.
2.
Your function getNewQbutton is an #IBAction that returns nothing. So the statement String(getNewQbutton()) doesn't make a lot of sense. Since your function getNewQbutton has no return type (and is an #IBOutlet), you are probably giving nil to String(). That may be the second reason of this issue.