I am trying to create a tableView of users from my Parse database that are in the same class (at school). All users have to have a username, but not all will have given the app their full name or set a profile picture. I use this code:
let studentsQuery = PFQuery(className:"_User")
studentsQuery.whereKey("objectId", containedIn: studentsArray! as! [AnyObject])
let query2 = PFQuery.orQueryWithSubqueries([studentsQuery])
query2.findObjectsInBackgroundWithBlock {
(results: [PFObject]?, error: NSError?) -> Void in
if error != nil {
// Display error in tableview
} else if results! == [] {
spinningActivity.hideAnimated(true)
print("error")
} else if results! != [] {
if let objects = results {
for object in objects {
if object.objectForKey("full_name") != nil {
let studentName = object.objectForKey("full_name")! as! String
self.studentNameResults.append(studentName)
}
if object.objectForKey("username") != nil {
let studentUsername = object.objectForKey("username")! as! String
self.studentUsernameResults.append(studentUsername)
}
if object.objectForKey("profile_picture") != nil {
let studentProfilePictureFile = object.objectForKey("profile_picture") as! PFFile
studentProfilePictureFile.getDataInBackgroundWithBlock({ (image: NSData?, error: NSError?) in
if error == nil {
let studentProfilePicture : UIImage = UIImage(data: image!)!
self.studentProfilePictureResults.append(studentProfilePicture)
} else {
print("Can't get profile picture")
// Can't get profile picture
}
self.studentsTableView.reloadData()
})
spinningActivity.hideAnimated(true)
} else {
// no image
}
}
}
} else {
spinningActivity.hideAnimated(true)
print("error")
}
}
This code works fine if all of the users have a username, full_name, and a profile_picture. I can't figure out, however, how to get a tableView of the usernames of a user and add a user's name or picture to the user's corresponding tableViewCell only if the user has a picture. Here is how my tableView is configured:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return studentUsernameResults.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("studentsCell", forIndexPath: indexPath) as! StudentsInClassInformationTableViewCell
cell.studentProfilePictureImageView.layer.cornerRadius = cell.studentProfilePictureImageView.frame.size.width / 2
cell.studentProfilePictureImageView.clipsToBounds = true
cell.studentProfilePictureImageView.image = studentProfilePictureResults[indexPath.row]
cell.studentUsernameLabel.text = studentUsernameResults[indexPath.row]
cell.studentNameLabel.text = studentNameResults[indexPath.row]
return cell
}
The studentProfilePictureResults, studentUsernameResults, and studentNameResults come from arrays of the user's picture, username, and name results pulled from Parse. If a user does not have a profile picture, I get the error, Index is out of range. Obviously, this means that there are, say, three names, three usernames, and only two pictures and Xcode doesn't know how to configure the cell. My question: How can I set a tableView up of a user's username and place their name and profile picture in the same cell, only if they have one?
Trying to store the different attributes in different arrays will be a problem, since as you have found, you end up with problems where a particular user doesn't have an attribute. You could use an array of optionals, so that you could store nil for an absent attribute, but it is much simpler to store the PFObject itself in a single array and accessing the attributes in cellForRowAtIndexPath rather than splitting out the attributes.
Since fetching the photo requires a separate, asynchronous, operation, you can store it separately. Rather than using an array to store the retrieved photos, which would have the same problem of ordering, you can use a dictionary, indexed by the user id; although for a large number of students it would probably be more efficient to use something like SDWebImage to download the photos as required in cellForRowAtIndexPath.
// these are instance properties defined at the top of your class
var students: [PFObject]?
var studentPhotos=[String:UIImage]()
// This is in your fetch function
let studentsQuery = PFUser.Query()
studentsQuery.whereKey("objectId", containedIn: studentsArray! as! [AnyObject])
let query2 = PFQuery.orQueryWithSubqueries([studentsQuery])
query2.findObjectsInBackgroundWithBlock {
(results: [PFObject]?, error: NSError?) -> Void in
guard (error == nil) else {
print(error)
spinningActivity.hideAnimated(true)
return
}
if let results = results {
self.students = results
for object in results {
if let studentProfilePictureFile = object.objectForKey("profile_picture") as? PFFile {
studentProfilePictureFile.getDataInBackgroundWithBlock({ (image: NSData?, error: NSError?) in
guard (error != nil) else {
print("Can't get profile picture: \(error)")
return
}
if let studentProfilePicture = UIImage(data: image!) {
self.studentPhotos[object["username"]!]=studentProfilePicture
}
}
}
spinningActivity.hideAnimated(true)
self.tableview.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.students != nil {
return self.students!.count
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("studentsCell", forIndexPath: indexPath) as! StudentsInClassInformationTableViewCell
cell.studentProfilePictureImageView.layer.cornerRadius = cell.studentProfilePictureImageView.frame.size.width / 2
cell.studentProfilePictureImageView.clipsToBounds = true
let student = self.students[indexPath.row]
if let studentPhoto = self.studentPhotos[student["username"]!] {
cell.studentProfilePictureImageView.image = studentProfilePictureResults[indexPath.row]
} else {
cell.studentProfilePictureImageView.image = nil
}
cell.studentUsernameLabel.text = student["username"]!
if let fullName = student["full_name"] {
cell.studentNameLabel.text = fullName
} else {
cell.studentNameLabel.text = ""
return cell
}
A few other pointers;
The use of _ to separate words in field names isn't really used in the iOS world; camelCase is preferred, so fullName rather than full_name
It looks like your Parse query could be more efficient if you had a class field or reference object so that you didn't need to supply an array of other class members.
Related
In my Xcode Project I will like to have a similar view like Snapchat's "Send To..." screen (I have attached a screenshot). I have already made a tableview and populate it and have allowed multiple selection on. I am currently having trouble with two things:
1) Multiple Selection: I can select an cell I want, but when I tap on the search bar and start typing, all my previous selections go away. I am assuming that I need to add all of the names in a array and somehow communicate the array with the table so it shows if this username is in the array then make it selected in the tableview. But I am not sure how to do that. How can I do this?
2) Sending to Bottom Bar (blue in photo): As you may know, in Snapchat as you press on which users you want to send the snap to, their names get added to the bar at the bottom, as you fill up the bar, it because swipe able where you can horizontally scroll through the names you have added. I can append the names to an array and show the array in a label like theirs, but I do not know how to make it so a user can horizontally scroll through it.How do I implement this same feature?
Feel free to answer ANY of the questions! You do not need to do all of them, I just need them answered. Here's my code so far:
class User {
var userID:String?
var userFullName:String?
var userUsername:String?
var userProfileImage:PFFile?
var isPrivate:Bool
init(userID : String, userFullName : String, userUserName : String, userProfileImage : PFFile, isPrivate : Bool) {
self.userID = userID
self.userFullName = userFullName
self.userUsername = userUserName
self.userProfileImage = userProfileImage
self.isPrivate = isPrivate
}
}
var userArray = [User]()
func loadFriends() {
//STEP 1: Find friends
let friendsQuery = PFQuery(className: "Friends") //choosing class
friendsQuery.whereKey("friendOne", equalTo: PFUser.current()?.objectId ?? String()) //finding friends
friendsQuery.limit = self.page //number of users intitally showing
friendsQuery.findObjectsInBackground (block: { (objects, error) -> Void in
if error == nil { //if no error
//clean up
self.friendsArray.removeAll(keepingCapacity: false)
//STEP 2: Find related objects depending on query setting
for object in objects! {
self.friendsArray.append(object.value(forKey: "friendTwo") as! String) //hold array info of friend
}
//STEP 3: Find friend info
let query = PFUser.query()
query?.whereKey("objectId", containedIn: self.friendsArray)
query?.addDescendingOrder("createdAt") //how to order users
query?.findObjectsInBackground(block: { (objects, error) -> Void in
if error == nil {
for object in objects! {
var user : User
let fullname = (object.value(forKey: "fullname") as! String)
let username = (object.object(forKey: "username") as! String)
let profilePhoto = (object.object(forKey: "profilePhoto") as! PFFile)
let objectID = (object.objectId!)
let isPrivate = (object.object(forKey: "isPrivate") as! Bool)
user = User(userID: objectID, userFullName: fullname, userUserName: username, userProfileImage: profilePhoto, isPrivate: isPrivate)
self.userArray.append(user)
}
self.tableView.reloadData()
} else {
print(error!)
}
})
} else {
print(error!)
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! FriendCell
let user = userArray[indexPath.row]
//add user info to cells
cell.fullnameLabel.text = user.userFullName
cell.usernameLabel.text = user.userUsername
cell.objectID = user.userID!
cell.isPrivate = user.isPrivate
user.userProfileImage?.getDataInBackground (block: { (data, error) in
if error == nil {
cell.profilePhoto.image = UIImage(data: data!)
}
})
})
}
1) Multiple Selection:
You should have a User class (e.g User) that holds user properties instead of maintaining array for each property. Store User object in a Array. User class could be like below:
class User {
var userID:String
var userFullName:String
var userName:String
var userProfileImageUrl:String
init(userID:String,userFullName:String,userName:String,userProfileImageUrl:String) {
self.userID = userID
self.userFullName = userFullName
self.userName = userName
self.userProfileImageUrl = userProfileImageUrl
}
}
You could have a User extension to check if that user is selected or not(e.g isSelected).
import UIKit
import Foundation
private var selectedKey: UInt8 = 0
extension User {
var isSelected:Bool{
get {
return objc_getAssociatedObject(self, &selectedKey) as! Bool
}
set {
objc_setAssociatedObject(self, &selectedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Now in your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell check that user.isSelected == true/false and update your selected/deselected image accordingly.
And update the value of isSelected in func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
2) Sending to Bottom Bar:
For bottom bar add a UICollectionView as a subview in UIView. Create a class overriding UICollectionViewCell that holds a UILabel. You can add flow layout in UICollectionView.
I have given just an idea to start with.Hope it will help you.
I think, you set bool check for every cell in tableView. If cell load again, it will not show check. Because, It check is false.
Let me preface this question by saying that I'm very new to coding with Swift and in iOS in general. I've got some experience in Java/Android and am starting to work with iOS as well now.
I need to query a dynamodb table with enough data in it that Amazon paginates the results (I think the limit is 100kb). Using the limited examples for AWS/Swift I am able to query that table but only successfully retrieve the first page of data. My question is how to get that 2nd, 3rd, etc page of data. See code below
let queryExpression = AWSDynamoDBQueryExpression()
queryExpression.keyConditionExpression = "venue_event = :ev"
queryExpression.expressionAttributeValues = [":ev" : "event"]
dynamoDBObjectMapper.query(Event.self, expression: queryExpression).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {(task:AWSTask!) -> AnyObject! in
let results = task.result as! AWSDynamoDBPaginatedOutput
for r in results.items{
print (r)
}
return nil
})
I've noticed that 'results' has a lastEvaluatedKey variable and loadNextPage method. However I can't seem to get either to give me the function I'm looking for
Thanks in advance for the help
Simply:
var paginatedOutput: AWSDynamoDBPaginatedOutput?
...
self.paginatedOutput = task.result as! AWSDynamoDBPaginatedOutput
...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("NoSQLQueryResultCell", forIndexPath: indexPath) as! NoSQLQueryResultCell
...
if (!loading) && (paginatedOutput?.lastEvaluatedKey != nil) && indexPath.section == self.results!.count - 1 {
self.loadMoreResults()
}
return cell
}
func loadMoreResults() {
loading = true
paginatedOutput?.loadNextPageWithCompletionHandler({(error: NSError?) -> Void in
if error != nil {
print("Failed to load more results: \(error)")
dispatch_async(dispatch_get_main_queue(), {
self.showAlertWithTitle("Error", message: "Failed to load more more results: \(error?.localizedDescription)")
})
}
else {
dispatch_async(dispatch_get_main_queue(), {
self.results!.appendContentsOf(self.paginatedOutput!.items)
self.tableView.reloadData()
self.loading = false
})
}
})
}
You can download MobileHub Sample code:
This map help:
I have a Parse query that returns a set of users (PFUsers). I place them in an array so that they can be used to populate a tableView. However, when I load the tableView I get the following error message: Could not cast value of type 'PFObject' to 'NSArray'. Here's the relevant code (I cut out some stuff to make it easier to read). It's heavily condensed but I can create a full gist of needed.
The error is caught on the line: self.realMatches = result as! [PFObject]
import UIKit
class MatchesViewController: BaseViewController {
var realMatches: [PFObject] = []
func loadMatches() {
if let user = self.user {
query{
(results: [AnyObject]?, error: NSError?) -> Void in
if error != nil {
println(error)
} else {
if results != nil {
self.matchesResults = results!
for result in results!{
if result.objectId != self.currentUser!.objectId {
self.realMatches = result as! [PFObject]
}
}
for result in results! {
self.user1 = result["user1"] as! PFUser
self.user2 = result["user2"] as! PFUser
}
self.tableView.reloadData()
}
}
}
} else {
println("current user doesnt exist")
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("MatchCell", forIndexPath: indexPath) as! UITableViewCell
let object = matchesResults[indexPath.row]
return cell
How can I safely store the PFUsers in an array to be used for the tableView?
Thanks!!
result is a single PFObject that you have extracted from the array of results array using a for loop.
You should simply say
self.realMatches = result as! PFObject
but self.realMatches is an array, so that assignment won't work either. You can append the result to the array using
self.realMatches.append(result as! PFObject)
I have a pointer in my parse. The pointer tells me who uploaded the images. I am trying to retrieve the username and the profile picture of the uploader. To do that I have put query.includeKey("uploader") . Users are managed through the user class. and posts are managed in the posts class. To retrieve the images and names I have the below code.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newview", forIndexPath: indexPath) as! NewCollectionViewCell
let item = self.votes[indexPath.row]
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
if let pointer = item["uploader"] as? PFObject {
cell.userName!.text = item["username"] as? String
}
if let profile = item["uploader"] as? PFObject {
cell.profileImageView.loadInBackground({ (image:UIImage, error:NSError) -> Void in
if error != nil{
cell.profileImageView.image = image
}
})}
if let votesValue = item["votes"] as? Int
{
cell.votesLabel?.text = "\(votesValue)"
}
// Fetch final flag image - if it exists
if let value = item["imageFile"] as? PFFile {
cell.postsImageView.file = value
cell.postsImageView.loadInBackground({ (image: UIImage?, error: NSError?) -> Void in
if error != nil {
cell.postsImageView.image = image
}
})
}
return cell
}
However errors are happening saying that loadinbackround can't be invoked with an argument list of type (UIImage, NSError)->void. The strange part is that the error is only for the first part where I try to retrieve the images for the user. I am really stuck in this and want help. Is my pointer retrieving wrong? Thank you.
UPDATE 2
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Posts")
query.includeKey("pointName")
query.findObjectsInBackgroundWithBlock{(question:[AnyObject]?,error:NSError?) -> Void in
if error == nil
{
if let allQuestion = question as? [PFObject]
{
self.votes = allQuestion
self.collectionView.reloadData()
}
}
}
// Wire up search bar delegate so that we can react to button selections
// Resize size of collection view items in grid so that we achieve 3 boxes across
loadCollectionViewData()
}
/*
==========================================================================================
Ensure data within the collection view is updated when ever it is displayed
==========================================================================================
*/
// Load data into the collectionView when the view appears
override func viewDidAppear(animated: Bool) {
loadCollectionViewData()
}
/*
==========================================================================================
Fetch data from the Parse platform
==========================================================================================
*/
func loadCollectionViewData() {
// Build a parse query object
}
/*
==========================================================================================
UICollectionView protocol required methods
==========================================================================================
*/
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.votes.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newview", forIndexPath: indexPath) as! NewCollectionViewCell
let item = self.votes[indexPath.row]
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
if let pointer = item["uploader"] as? PFObject {
cell.userName!.text = item["username"] as? String
print("username")
}
if let profile = item["uploader"] as? PFObject,
profileImageFile = profile["profilePicture"] as? PFFile {
cell.profileImageView.file = profileImageFile
cell.profileImageView.loadInBackground { image, error in
if error == nil {
cell.profileImageView.image = image
}
}
}
if let votesValue = item["votes"] as? Int
{
cell.votesLabel?.text = "\(votesValue)"
}
// Fetch final flag image - if it exists
if let value = item["imageFile"] as? PFFile {
println("Value \(value)")
cell.postsImageView.file = value
cell.postsImageView.loadInBackground({ (image: UIImage?, error: NSError?) -> Void in
if error != nil {
cell.postsImageView.image = image
}
})
}
return cell
}
In my post class it is like this
The users are managed in the user class. I want to get the profile image and username of the person who posted the image.
In the user class I have all the user information.
cell.postsImageView.loadInBackground({
(image: UIImage!, error: NSError!) -> Void in
if error == nil {
if image != nil {
dispatch_async(dispatch_get_main_queue(),{
cell.postsImageView.image = image
})
}else{
println("Image not available")
}
}else{
println(Image Downloading error: \(error))
}
})
Try this , i think this will help you :)
Try to replace your loadInBackground method with the below:
cell.postsImageView.loadInBackground({ (image: UIImage!, error: NSError!) -> Void in
if error == nil {
cell.postsImageView.file = value
cell.postsImageView.image = image
}
})
}
You have two issues here, the first being the way you pass the closure to the loadInBackground method
The compiler error is because you're trying to call the method with non-optional closure parameters.
In the cell.postsImageView.loadInBackground call you use optionals, while in the cell.profileImageView.loadInBackground you don't.
Closure parameter types are important, that's why the compiler is complaining.
I'd suggest skipping the types and defining the closure like this:
cell.profileImageView.loadInBackground { image, error in
if error != nil{
cell.profileImageView.image = image
}
})}
The other and probably your main issue is that you don't set a file for the profile. So, if we say that the profile image is stored under imageFile in the uploader object, you would use:
if let profile = item["uploader"] as? PFObject,
profileImageFile = profile["imageFile"] as? PFFile {
cell.profileImageView.file = profileImageFile
cell.profileImageView.loadInBackground { image, error in
if error == nil {
cell.profileImageView.image = image
}
}
}
Though, as the Parse documentation says, the method downloads and displays the image:
Once the download completes, the remote image will be displayed.
https://parse.com/docs/ios/api/Classes/PFImageView.html#//api/name/loadInBackground:
If you don't need any special error handling and since you already have a placeholder image, try loading the image like this:
if let profile = item["uploader"] as? PFObject,
profileImageFile = profile["imageFile"] as? PFFile {
cell.profileImageView.file = profileImageFile
cell.profileImageView.loadInBackground()
}
I am trying to retrieve user profile image from parse. I have a collection view and I am retrieving all images people posted. I want to show each users profile image in the cell as well. I was using the below code
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Posts")
query.includeKey("pointName")
query.findObjectsInBackgroundWithBlock{(question:[AnyObject]?,error:NSError?) -> Void in
if error == nil
{
if let allQuestion = question as? [PFObject]
{
self.votes = allQuestion
self.collectionView.reloadData()
}
}
}
// Wire up search bar delegate so that we can react to button selections
// Resize size of collection view items in grid so that we achieve 3 boxes across
loadCollectionViewData()
}
/*
==========================================================================================
Ensure data within the collection view is updated when ever it is displayed
==========================================================================================
*/
// Load data into the collectionView when the view appears
override func viewDidAppear(animated: Bool) {
loadCollectionViewData()
}
/*
==========================================================================================
Fetch data from the Parse platform
==========================================================================================
*/
func loadCollectionViewData() {
// Build a parse query object
}
/*
==========================================================================================
UICollectionView protocol required methods
==========================================================================================
*/
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.votes.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newview", forIndexPath: indexPath) as! NewCollectionViewCell
let item = self.votes[indexPath.row]
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
if let pointer = item["uploader"] as? PFObject {
cell.userName!.text = item["username"] as? String
print("username")
}
if let profile = item["uploader"] as? PFObject,
profileImageFile = profile["profilePicture"] as? PFFile {
cell.profileImageView.file = profileImageFile
cell.profileImageView.loadInBackground { image, error in
if error == nil {
cell.profileImageView.image = image
}
}
}
if let votesValue = item["votes"] as? Int
{
cell.votesLabel?.text = "\(votesValue)"
}
// Fetch final flag image - if it exists
if let value = item["imageFile"] as? PFFile {
println("Value \(value)")
cell.postsImageView.file = value
cell.postsImageView.loadInBackground({ (image: UIImage?, error: NSError?) -> Void in
if error != nil {
cell.postsImageView.image = image
}
})
}
return cell
}
However I found out that it sets profile image to the current user and not the user who posted the image. How can I do this? Thank you
UPDATE
so In parse my post class is
so I know who uploaded it but I don't know how to retrieve the profile image for this specific user.
You need to use a pointer that will point to the user who created the object. The profile photo should be in the user class. You then include the pointer in your query and that will return the user data.
okay, this might help you in objective-C
PFUser *user = PFUser *user = [PFUser currentUser];
[user fetchIfNeededInBackgroundWithBlock:^(PFObject *object, NSError *error) {
_profileImage.file = [object objectForKey:#"profilePicture"];
}];