Swift 1.2 and Parse: Issue with retrieving images to populate PFQueryCollectionViewController - ios

I'm currently working on an application that displays images at certain locations. The issue is the following:
When the user clicks on the location in the MapView it goes to an empty collection view. If the user pulls to refresh, the spinner is active but the images do not load. However, if the user goes back to the MapView and then clicks on the location again, the images are loaded in the collection view. Of course we are trying to get the images loaded when the user first goes to the collection view.
I think is likely a small issue, but after hours of trying different various recommendations from different sources we simply cannot get it to work properly.
Any help would be very much appreciated.
Thank you.
Here is my code for the PFQueryCollectionViewController:
import UIKit
class PhotoGridCollectionViewController: PFQueryCollectionViewController {
var savedPics: [UIImage]?
func loadImages() -> PFQuery {
var query = PFQuery(className: "UserPhoto")
query.orderByDescending("timeTaken")
return query
}
override func viewDidAppear(animated: Bool) {
}
override func viewDidLoad() {
super.viewDidLoad()
loadImages()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return self.objects.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFCollectionViewCell? {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("venuePhotoThumb", forIndexPath: indexPath) as! PhotoGridCollectionViewCell
if let pfObject = object {
cell.imageView.file = pfObject["imageFile"] as? PFFile
cell.imageView.loadInBackground({ (img, err) -> Void in
println("Download complete")
})
}
return cell
}
}
EDIT: I have now updated my code and it is a bit cleaner, but I'm still having the exact same issue.

In my previous answer I just used a plain old UICollectionViewController to solve the issue, but after a lot of digging I finally figured out how to correctly implement a PFQueryCollectionViewController.
There must be a placeholder image in the PFQueryCollectionViewController or your images will not load when the user first loads the PFQueryCollectionViewController.
Here is a bit of the code to illustrate this:
import UIKit
import Parse
import ParseUI
class PhotoCollectionViewController: PFQueryCollectionViewController {
...
var parseObject: PFObject!
var placeHolderView: UIView!
...
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = UIEdgeInsetsMake(5.0, 10.0, 5.0, 10.0)
layout.minimumInteritemSpacing = 5.0
}
}
...
// MARK: PFQuery
override func queryForCollection() -> PFQuery {
let query = super.queryForCollection()
query.whereKey("name", equalTo: parseObject!)
query.orderByDescending("date")
return query
}
override func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath,
object: PFObject?) -> PFCollectionViewCell? {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell",
forIndexPath: indexPath) as? CustomCollectionViewCell
...
// HERE YOU MUST HAVE A PLACEHOLDER IMAGE
var initialThumbnail = UIImage(named: "thumbnail")
cell?.collectionImageView.image = initialThumbnail
if let imageFile = object?["image"] as? PFFile {
cell?.collectionImageView.file = imageFile
cell?.collectionImageView.loadInBackground()
}
return cell
}
...
}
The answer was quite simple, but as I am relatively new to iOS development it took some time to finally figure it out. The necessity for the placeholder image applies to both the PFQueryCollectionViewController and PFQueryTableViewController.

Related

How to reload tableView data after data is passed by a Segue

I have two table views. One which the user clicks on and one where data is displayed. When the user clicks on a cell in the first table view a query is made to my firebase database and the query is stored in an Array. I then pass the data through a segue. I used a property observer so I know that the variable is being set. By using break points I was able to determine that my variable obtains its value right before the cellForRowAtIndexPath method. I need help displaying the data in my table view. I do not know where to reload the data to get the table view to update with my data. I am using Swift.
EDIT 2: I have solved my problem. I will post my first and second table views so that you can see my solution.
FirstTableView
import UIKit
import Firebase
import FirebaseDatabase
class GenreTableViewController: UITableViewController {
let dataBase = FIRDatabase.database()
var genreArray = ["Drama","Classic,Comic/Graphic novel","Crime/Detective","Fable,Fairy tale","Fantasy","Fiction narrative", "Fiction in verse","Folklore","Historical fiction","Horror","Humour","Legend","Magical realism","Metafiction","Mystery","Mythology","Mythopoeia","Realistic fiction","Science fiction","Short story","Suspense/Thriller","Tall tale","Western,Biography","Autobiography","Essay","Narrative nonfiction/Personal narrative","Memoir","Speech","Textbook","Reference book","Self-help book","Journalism", "Religon"]
var ResultArray: [NSObject] = []
var infoArray:[AnyObject] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return genreArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel?.text = genreArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DestViewController: ResultTableViewController = segue.destinationViewController as! ResultTableViewController
if segue.identifier == "letsGo" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let tappedItem = self.genreArray[indexPath.row]
DestViewController.someString = tappedItem
}
}
}
}
import UIKit
import Firebase
import FirebaseDatabase
class ResultTableViewController: UITableViewController {
let dataBase = FIRDatabase.database()
var SecondResultArray: [FIRDataSnapshot]! = []
var someString: String?{
didSet {
print("I AM A LARGE TEXT")
print(someString)
}
}
override func viewDidLoad() {
let bookRef = dataBase.reference().child("books")
bookRef.queryOrderedByChild("Genre")
.queryEqualToValue(someString)
.observeSingleEventOfType(.Value, withBlock:{ snapshot in
for child in snapshot.children {
self.SecondResultArray.append(child as! FIRDataSnapshot)
//print(self.ResultArray)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
})
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return SecondResultArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath)
// Configure the cell...
let bookSnapShot: FIRDataSnapshot! = self.SecondResultArray[indexPath.row]
let book = bookSnapShot.value as! Dictionary<String, String>
let Author = book["Author"] as String!
let Comment = book["Comment"] as String!
let Genre = book["Genre"] as String!
let User = book["User"] as String!
let title = book["title"] as String!
cell.textLabel?.numberOfLines = 0
cell.textLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping
cell.textLabel?.text = "Author: " + Author + "\n" + "Comment: " + Comment + "\n" + "Genre: " + Genre + "\n" + "User: " + User + "\n" + "Title: " + title
let photoUrl = book["bookPhoto"], url = NSURL(string:photoUrl!), data = NSData(contentsOfURL: url!)
cell.imageView?.image = UIImage(data: data!)
return cell
}
}
For better context and troubleshooting here is my current code for the tableView which is supposed to display data:
import UIKit
class ResultTableViewController: UITableViewController {
var SecondResultArray: Array<NSObject> = []{
willSet(newVal){
print("The old value was \(SecondResultArray) and the new value is \(newVal)")
}
didSet(oldVal){
print("The old value was \(oldVal) and the new value is \(SecondResultArray)")
self.tableView.reloadData()
}
}
override func viewDidLoad() {
print ("I have this many elements\(SecondResultArray.count)")
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return SecondResultArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath)
cell.textLabel?.text = SecondResultArray[indexPath.row] as? String
return cell
}
}
Edit:
Here is my first table view controller. I have tried using the completion handler, but I can't call it correctly and I am constricted by the fact that my query happens in the didSelectRowAtIndexPath method. Please help.
import UIKit
import Firebase
import FirebaseDatabase
class GenreTableViewController: UITableViewController {
let dataBase = FIRDatabase.database()
var genreArray = ["Drama","Classic,Comic/Graphic novel","Crime/Detective","Fable,Fairy tale","Fantasy","Fiction narrative", "Fiction in verse","Folklore","Historical fiction","Horror","Humour","Legend","Magical realism","Metafiction","Mystery","Mythology","Mythopoeia","Realistic fiction","Science fiction","Short story","Suspense/Thriller","Tall tale","Western,Biography","Autobiography","Essay","Narrative nonfiction/Personal narrative","Memoir","Speech","Textbook","Reference book","Self-help book","Journalism", "Religon"]
var ResultArray: [NSObject] = []
var infoArray:[AnyObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return genreArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = genreArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
typealias CompletionHandler = (result:NSObject?, error: NSError?) -> Void
func getData(completionHandeler: CompletionHandler){
let bookRef = self.dataBase.reference().child("books")
let GenreSelector = self.genreArray[indexPath.row]
bookRef.queryOrderedByChild("Genre")
.queryEqualToValue(GenreSelector)
.observeSingleEventOfType(.Value, withBlock:{ snapshot in
for child in snapshot.children {
print("Loading group \((child.key!))")
self.ResultArray.append(child as! NSObject)
}
print(self.ResultArray)
self.performSegueWithIdentifier("letsGo", sender: self)
self.tableView.reloadData()
})
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController: ResultTableViewController = segue.destinationViewController as! ResultTableViewController
DestViewController.SecondResultArray = self.ResultArray
}
You can inject the data to the destination viewController in prepareForSegue Method of the first UIViewController and reload your UITableView in viewDidAppear. If you are getting your data asynchronously, have a completionHandler and reload it in the completionHandler. Here is an example.
func fetchDataWithCompletion(response: (NSDictionary?, error:NSError?)-> Void) -> Void {
//make the API call here
}
How about this:
Assume you have an array (myArray) populated from Firebase and stored in the first tableViewController. There's a second tableViewController and a segue connecting them.
We want to be able to tap on an item in the first tableviewController, have the app retrieve detailed data for the item from Firebase (a 'data' node) and display the detailed data in the second tableViewController.
Firebase structure
some_node
child_node_0
data: some detailed data about child_node_0
child_node_1
data: some detailed data about child_node_1
Within the second tableViewContoller:
var passedObject: AnyObject? {
didSet {
self.configView() // Update the view.
}
}
Tapping an item in the first tableView calls the following function
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showListInSecondTable" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let tappedItem = myArray[indexPath.row] as! String
let keyOfTappedItem = tappedItem.firebaseKey //child_node_0 for example
doFirebase(keyOfTappedItem)
}
}
}
and the prepareForSegue then calls the following which loads the data from firebase and when the snapshot returns within the block, it populates the passedObject property in the second tableView
func doFirebase(firebaseKey: String) {
ref = myRootRef.childByAppendingPath("\(firebaseKey)/data")
//if we want the detailed data for child_node_0 this would resolve
// to rootRef/child_node_0/data
ref.observeSingleEventOfType(.Value, { snapshot in
let detailObjectToPass = snapshot.Value["data"] as! NSArray or string etc
let controller = (segue.destinationViewController as! UINavigationController).myViewController as! SecondViewController
controller.passedObject = detailObjectToPass
}
and of course in secondController, setting the passedArray calls didSet and sets up the view, and tells the tableView to reload itself, displaying the passed array.
func configView() {
//set up the view and buttons
self.reloadData()
}
I did this super quick so ignore the typos's. The pattern is correct and satisfies the question. (and eliminates the need for an observer to boot!)
P.S. this is way over coded but I wanted to demonstrate the flow and leveraging the asynchronous call to firebase to load the second tableView when the data was valid within the block.
Try updating your closure to include this:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
Edit:
On second read, you are already using a completion handler, but I think you didn't see it. Let me correct your code above a bit:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let bookRef = self.dataBase.reference().child("books")
let GenreSelector = self.genreArray[indexPath.row]
bookRef.queryOrderedByChild("Genre")
.queryEqualToValue(GenreSelector)
.observeSingleEventOfType(.Value, withBlock:{ snapshot in
// This here is your completion handler code!
// I assume it is called asynchronously once your DB is done
for child in snapshot.children {
print("Loading group \((child.key!))")
self.ResultArray.append(child as! NSObject)
}
print(self.ResultArray)
self.performSegueWithIdentifier("letsGo", sender: self)
// self.tableView.reloadData() // is this really needed
})
}
}
You defined a closure, but simply didn't call it. I don't see a reason for that anyways, assuming the block gets called once the database gives you your results. Am I missing something?
That's a good start already, but I think you didn't entirely get how to use a completion handler in this regard, but of course I may be wrong.
I built on top of user3861282's answer and created a small demo project at my github.
In short: You can do all inter-table-communication in the prepareForSegue: method of your first table view controller. Configure the second table view controller there (via its vars). Not any closures/completion handlers there yet.
Then in the second view controller's viewWillAppear: method, start the loading (including an animation if you want). I suggest something like NSURLSession that already defines a completion handler. In that you work with your data from remote, stop any loading animations and you're good.
If the completion handler must be defined in the first table view controller, you can even set it as a var in the second table view controller. That way you "hand over" the closure, i.e. "piece of code".
Alternatively, you can start animations and remote request in the first table view controller and then performSegueWithIdentifier once that is done. In your question you wrote that you want to load in the second table view controller, however, if I understood you correctly.
Your code above properly defines a closure that expects a completion handler (which is also a closure and so kind of doubles what you want), but you never actually call it somewhere. Nor do you call the completion handler in the closure. See my demo for how it can work.
The project I wrote illustrates just one way to do it (minus animations, not enough time). It also shows how you can define your own function expecting a completion handler, but as I said, the standard remote connections in the framework provide one anyways.
Based on additional code that was added to the post, the issue is a controller variable going out of scope.
So here's the issue
class MyClass {
func setUpVars {
let x = 1
}
func doStuff {
print(x)
}
}
Create a class and attempt to print the value of x
let aClass = MyClass()
aClass.setUpVars
aClass.doStuff
This will print nothing (conceptually) as once setUpVars ended, the 'x' variable went out of scope.
whereas
class MyClass {
var x: Int
func setUpVars {
x = 1
}
func doStuff {
print(x)
}
}
will print the value of x, 1.
So the real solution is that your viewControllers need to 'stay alive' during the duration of your class (or app).
Here's the pattern. In the MasterViewController
import UIKit
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
then in your MasterViewController viewDidLoad (or wherever), create the detailViewController
override func viewDidLoad() {
super.viewDidLoad()
let controllers = split.viewControllers //this is from a splitViewController
self.detailViewController =
controllers[controllers.count-1].topViewController as? DetailViewController
}
and from there you have it... use prepareForSegue to 'send' the data to the detailViewController
Just wanted to have this posted for future reference.
You can reload the TableView with [tableView reloadData];.

prepareForSegue empties my NSManagedObject Array

I need to pass data from one view to another in the current app I'm working on with the latest version of Swift for iOS 8. I fill the FacultyArray using a function that gets the information from CoreData using MagicalRecord. The problem I encounter is that, when I try to use the filled array in the prepareForSegue function turns out it's empty.
import UIKit
import SwiftyJSON
class EspecialidadTVC: UITableViewController {
let defaults = NSUserDefaults.standardUserDefaults()
let endpoint = Connection()
var facultyList:Array<Faculty>? = TR_Faculty().get()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources thavaran be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
//return self.facultyArray!.count
return (self.facultyList?.count)!
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("facultyCell", forIndexPath: indexPath)
cell.textLabel?.text = self.facultyList![indexPath.row].nombre
return cell
}
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "presentMainMenuSegue" {
let splitViewController:MasterSVC = segue.destinationViewController as! MasterSVC
let indexpath = self.tableView.indexPathForSelectedRow
splitViewController.faculty = self.facultyList![indexpath!.row]
//splitViewController.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
}
if segue.identifier == "logoutSegue" {
}
}
}
Is there a better way to send an NSManagedObject between views?
UPDATE
Here's the destination code for MasterSVC. There's no console error, just that my array gets emptied, so when I send the object, this is a nil object.
import UIKit
import Foundation
class MasterSVC: UISplitViewController {
var faculty:Faculty!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem()
navigationItem.leftItemsSupplementBackButton = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
}
}
UPDATE 2
Method that fills the array
class TR_Faculty {
let dateFormatter = NSDateFormatter()
func store(json: JSON) {
//Clear previous data
Faculty.MR_truncateAll()
for (index,subJson):(String, JSON) in json {
let fac = Faculty.MR_createEntity()
fac?.id = Int(subJson["IdEspecialidad"].stringValue)
fac?.codigo = subJson["Codigo"].stringValue
fac?.nombre = subJson["Nombre"].stringValue
fac?.descripcion = subJson["Descripcion"].stringValue
fac?.updated_at = self.dateFormatter.dateFromString(subJson["updated_at"].stringValue)
}
NSManagedObjectContext.MR_defaultContext().MR_saveToPersistentStoreAndWait()
}
func get()-> Array<Faculty>?{
return Faculty.MR_findAll() as! Array<Faculty>
}
}
Well the problem that I had is that the call to the service is asynchronous, so nothing guarantees that the call is gonna finish before you perform a segue to another view controller. What worked for me is just placing performSegueWithIdentifier inside the success part of the call.
As a tip for other people just starting to use API's in mobile applications, when you wanna call a restful API from your app, you should make the call, wait until it successfully finishes and then populate the SQLite database. Afterwards you can make the call to the database to populate the view.

Images not appearing in PFQueryCollectionViewController

I'm working on an iPhone app that should display images in a UICollectionView. The images are saved on the Parse cloud, and I'm using a subclass of PFQueryCollectionViewController to make the query and display the images.
Tapping on a MKMapView callout triggers the segue that shows the CollectionViewController. The images do not appear in the cells the first time the CollectionViewController is presented. However, if I go back to the MapView, and then return to the CollectionViewController, the images appear in the cells.
How can I get the images to appear the first time the PFQueryCollectionViewController is presented?
Here's my code:
class PhotoGridCollectionViewController: PFQueryCollectionViewController {
override func viewDidAppear(animated: Bool) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return self.objects.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFCollectionViewCell? {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("venuePhotoThumb", forIndexPath: indexPath) as! PhotoGridCollectionViewCell
if let pfObject = object {
cell.imageView.file = pfObject["imageFile"] as? PFFile
cell.imageView.loadInBackground({ (img, err) -> Void in
println("Download complete")
})
}
return cell
}
}
Since I'm using Storyboards, I pass my custom class the parseClass by setting it in the Attribute Inspector:
As you can see, I'm using the loadInBackground method of the PFImageView to load the images asynchronously, and I think this the problem might be caused by the images being downloaded after the cell is returned, but I don't have any other ideas. Does anyone know why the images are not appearing the first time the view is presented?
My co-developer finally found the answer to this. We had to set a placeholder image for the PFImageViews when configuring the cells in cellForItemAtIndexPath. The code examples on Parse do include setting the placeholder image, but they don't say that it is required for the PFQueryCollectionViewController to work properly. We considered it an aesthetic touch that we could come back to later.
So the answer is: in order for images to load properly in the cells of a PFQueryCollectionViewController you MUST provide a placeholder image when configuring the cells in cellForItemAtIndexPath.
Here's the code that worked:
let placeholder = UIImage(named: "myPlaceholderImage") //an image from images.xcassets in your xcode project
cell.imageView.image = placeholder
if let pfObject = object {
cell.imageView.file = pfObject["image"] as? PFFile
cell.imageView.loadInBackground()
}

How to pass multiple value in numberOfItemsSection in Xcode 6 using Swift?

I have been through this for hours, including google the solution and some tutorial, but I couldn't found it. I'm still new to the Swift and Xcode 6.
I don't know whats wrong with the coding. (Exactly I think I knew).
I want to use property list to attach to the label in my project.
import UIKit
let reuseIdentifier = "Cell"
class TransportCollectionView: UICollectionViewController {
var trans = Array<String>()
var labelTrans:AnyObject?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
//self.collectionView!.registerClass(UICollectionViewCell.self,forCellWithReuseIdentifier: reuseIdentifier)
// Do any additional setup after loading the view.
let path = NSBundle.mainBundle().pathForResource("transport", ofType: "plist")
let labeltrans2 = NSArray(contentsOfFile: path!)
labelTrans = labeltrans2!
trans = ["alorgajah3.png", "ayerkeroh3.png", "ayermolek3.png","batuberendam3.png", "bertamulu3.png", "batangmelaka3.png","bukitkatil3.png", "bukitrambai3.png", "jasin3.png","kemterendak3.png", "krubong3.png", "kualalinggi3.png","masjidtanah3.png", "merlimau3.png", "mitc3.png", "muar3.png","pantaikundor3.png", "payaikan3.png", "pengkalankempas3.png","pokokmangga3.png", "pulaugadong3.png", "tampin3.png", "tangkak3.png"]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return trans.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : TransportCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",forIndexPath: indexPath) as TransportCell
cell.transImg?.image = UIImage(named: trans[indexPath.row])
cell.transLabel!.text = toString(labelTrans![indexPath.row]!.objectForKey("name")!)
return cell
}
the error said
fatal error: unexpectedly found nil while unwrapping an Optional value
Yes. I know my coding not assigned any value to the labelTrans. Can somebody help me, on how to assigned multiple value?
You have two places where you are unwrapping an optional that can be nil. To debug that (or better, to avoid these kind of crashes) it's better just to use the if let unwrapping pattern.
// rewrite this:
let labeltrans2 = NSArray(contentsOfFile: path!)
labelTrans = labeltrans2!
// as this:
if let labeltrans2 = NSArray(contentsOfFile: path!) {
labelTrans = labeltrans2
}
This way, if labeltrans2 is nil, nothing inside the if block gets executed. If labeltrans2 if not nil, the labeltrans2 constant gets created.
You should check that:
cell.transLabel
it's a valid, connected IBOutlet
Also, this piece of code:
labelTrans![indexPath.row]!.objectForKey("name")!
it's very convoluted, just because you defined labelTrans to be of type AnyObject?. But it's an NSArray, so if you define it like:
var labelTrans: NSArray<String>?
you can do:
if let text = labelTrans[indexPath.row] {
// use text
}
Side note:
Defining let reuseIdentifier = "Cell" outside your class makes no sense: it's then a global constant. You should use this constant inside collectionView:cellForItemAtIndexPath:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let reuseIdentifier = "Cell"
// and actually use it here
let cell : TransportCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier ,forIndexPath: indexPath) as TransportCell
...

In my Tab Bar View, my TableView cannot load data when launched but data would then appear when I click away and click back

I am trying to load a list of titles from reddit.com/.json, but my tableview under a UIViewController would not load data whenever I launch it, the screen would be blank like this:
http://imgur.com/ucriMht
I have to click the Downloads Tab and then click back to the Current Sub tab in order for my TableView to properly load data from the json file:
http://imgur.com/TJyW65O
I also attach my code of the ViewController for that tab in this question to see if anyone can spot where I have done wrong.
import UIKit
class CurrentSubViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,NSURLConnectionDelegate,UISearchBarDelegate {
var manager:DataManager = DataManager()
var localJson:JSON=JSON.nullJSON
#IBOutlet var tableView : UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
manager.startConnection()
print("subredditview loaded")
// Do any additional setup after loading the view.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return manager.json["data"]["children"].count
}
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = self.tableView?.dequeueReusableCellWithIdentifier("TitleCell",forIndexPath:indexPath) as UITableViewCell
let row=indexPath.row
print("got here /table view yeah")
cell.textLabel?.text = manager.json["data"]["children"][row]["data"]["title"].stringValue
cell.detailTextLabel?.text = manager.json["data"]["children"][row]["data"]["author_name"].stringValue
/**if !json["data"]["children"][row]["data"]["thumbnail"].stringValue.isEmpty
{
let thumbnailPath=json["data"]["children"][row]["data"]["thumbnail"].stringValue
let thumbnailURL:NSURL=NSURL(string: thumbnailPath)!
let thumbnailData=NSData(contentsOfURL:thumbnailURL)
let thumbnail=UIImage(data:thumbnailData!)
cell.imageView?.image=thumbnail
}**/
return cell
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
manager.query="http://reddit.com/r/"+searchBar.text+"/.json"
manager.data=NSMutableData()
manager.startConnection()
tableView?.reloadData()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
one way to solve this problem is start the networkActivityIndicator when data is receive from the server this way:
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
after that when data is received you can set network activity to false and when the network activity is done you can reload your tableView this way:
if UIApplication.sharedApplication().networkActivityIndicatorVisible == false{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
and your code will be something like this:
import UIKit
class CurrentSubViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,NSURLConnectionDelegate,UISearchBarDelegate {
var manager:DataManager = DataManager()
var localJson:JSON=JSON.nullJSON
#IBOutlet var tableView : UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
manager.startConnection()
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
print("subredditview loaded")
// Do any additional setup after loading the view.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return manager.json["data"]["children"].count
}
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = self.tableView?.dequeueReusableCellWithIdentifier("TitleCell",forIndexPath:indexPath) as UITableViewCell
let row=indexPath.row
print("got here /table view yeah")
cell.textLabel?.text = manager.json["data"]["children"][row]["data"]["title"].stringValue
cell.detailTextLabel?.text = manager.json["data"]["children"][row]["data"]["author_name"].stringValue
/**if !json["data"]["children"][row]["data"]["thumbnail"].stringValue.isEmpty
{
let thumbnailPath=json["data"]["children"][row]["data"]["thumbnail"].stringValue
let thumbnailURL:NSURL=NSURL(string: thumbnailPath)!
let thumbnailData=NSData(contentsOfURL:thumbnailURL)
let thumbnail=UIImage(data:thumbnailData!)
cell.imageView?.image=thumbnail
}**/
return cell
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
manager.query="http://reddit.com/r/"+searchBar.text+"/.json"
manager.data=NSMutableData()
manager.startConnection()
tableView?.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible == false
if UIApplication.sharedApplication().networkActivityIndicatorVisible == false{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I didn't test it but let me know if it works for you.
try this may be this will help you.

Resources