I have created a fully functioning tableview that populates its data from a text array (String) and an image array (PFFile). I have also implemented a search bar that displays the filtered results based on the created text array.
var fruitArray = [String]()
var imageFile = [PFFile]()
the problem is that fruitArray[indexPath.row] is filtered according to the input in the search bar but, the search bar cannot filter anything from imageFile and imageFile[indexPath.row] is displayed as if nothing has been searched.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print(fruitArray[indexPath.row])
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
if searchController.active && searchController.searchBar.text != "" {
cell.labelFruitName?.text = searchResults[indexPath.row]
} else {
cell.labelFruitName?.text = fruitArray[indexPath.row]
}
let placeHolder = UIImage(named: "plchlder.png")
cell.fruitImages?.image = placeHolder
imageFile[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.fruitImages?.image = downloadedImage
}
}
return cell
}
as a result, the images won't change inside the cell. in other words, before searching anything, if the first cell shows a text and picture of an apple, after the search, the first cell will always display an apple but the text and number of cells change.
Any solutions would be appreciated. Thanks in advance
I would suggest you create an array of type [fruit] , where fruit is just a struct with an image and a string. Then you can filter out the bad apples.
Here is the code for the struct:
struct Fruit {
let name: String
let image: PFFile
}
Then in your searchResultsUpdating function just filter out your search term using a simple filter:
results = fruitArray.filter{$0.name.containsString(searchController.searchBar.text!)}
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.
I have a tableview which acts as a newsfeed. The cells are filled from an array of newsfeed items. I get the JSON from the Server, create newsfeed items from that input and attach them to my newsfeed array. a newsfeed item contains a title, a description and an imageurl string.
At:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "ImageFeedItemTableViewCell1", for: indexPath) as! ImageFeedItemTableViewCell
var item = self.feed!.items[indexPath.row]
if (item.messageType == 1){
cell = tableView.dequeueReusableCell(withIdentifier: "ImageFeedItemTableViewCell1", for: indexPath) as! ImageFeedItemTableViewCell
cell.title.text = item.title
cell.description.text = item.contentText
if (item.imageURL as URL == URL(string: "noPicture")!)
{
cell.picture.image = UIImage(named:"empty")
}
else{
if (item.cachedImage == UIImage(named:"default-placeholder")){
let request = URLRequest(url: item.imageURL as URL)
cell.picture.image = item.cachedImage
cell.dataTask = self.urlSession.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
OperationQueue.main.addOperation({ () -> Void in
if error == nil && data != nil {
let image = UIImage(data: data!)
if (image != nil){
self.feed!.items[indexPath.row].cachedImage = image!
}
cell.picture.image = image
}
})
})
cell.dataTask?.resume()
}else
{
cell.picture.image = item.cachedImage
}
}
}
the cells from the rows get filled with my newsfeeditem data.
But since i keep all my newsfeeditems inside an array, the memory usage is high and gehts higher for each additional newsfeeditem. I want it to work with endless scrolling like twitter, so i wonder how experienced developers tackle this memory issue.
Your problem is in this lines or wherever you try to hold UIImage inside your array, this is really not advised and will cause crash due to memory since image is very large data and not advised to persist it in your RAM with UIImage inside array:
self.feed!.items[indexPath.row].cachedImage = image!
What you need to do is basically after fetch your image from URL, you save it to your app's documents folder and save the name or it's path that can distinct your image in cachedImage (just change the type to string or sth) and refetch it from your app's document folder when you need to show it in cellForRow
Flow: Fetch image -> save to disk and persist path in array -> refetch from disk with the path in cellForRow
I have added UITableView into UIScrollView, I have created an IBOutlet for height constraint of UITableView which helps me in setting the content size of UITableview.
I have 3 tabs and I switch tabs to reload data with different data source . Also the i have different custom cells when the tab changes.
So when the tab changes I call reloadData
here is my cellForRow function
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
var cell:UITableViewCell!
let event:Event!
if(tableView == self.dataTableView)
{
let eventCell:EventTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier, forIndexPath: indexPath) as! EventTableViewCell
eventCell.delegate = self
event = sectionsArray[indexPath.section].EventItems[indexPath.row]
eventCell.eventTitleLabel?.text = "\(event.title)"
eventCell.eventImageView?.image = UIImage(named: "def.png")
if let img = imageCache[event.imgUrl] {
eventCell.eventImageView?.image = img
}
else {
print("calling image of \(indexPath.row) \(event.imgUrl)")
// let escapedString = event.imgUrl.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
let session = NSURLSession.sharedSession()
do {
let encodedImageUrl = CommonEHUtils.urlEncodeString(event.imgUrl)
let urlObj = NSURL(string:encodedImageUrl)
if urlObj != nil {
let task = session.dataTaskWithURL(urlObj!, completionHandler: { ( data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
guard let realResponse = response as? NSHTTPURLResponse where
realResponse.statusCode == 200 else {
print("Not a 200 response, url = " + event.imgUrl)
return
}
if error == nil {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data!)
// Store the image in to our cache
self.imageCache[event.imgUrl] = image
// Update the cell
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate:EventTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? EventTableViewCell {
cellToUpdate.eventImageView?.image = image
}
})
}
})
task.resume()
}
} catch {
print("Cant fetch image \(event.imgUrl)")
}
}
cell = eventCell
}
else if(secodTabClicked)
{
let Cell2:cell2TableViewCell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier1, forIndexPath: indexPath) as! cell2TableViewCell
//Image loading again takes place here
cell = Cell2
}
else if(thirdTabClicked)
{
let Cell3:cell3TableViewCell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier2, forIndexPath: indexPath) as! cell3TableViewCell
//Image loading again takes place here
cell = Cell3
}
return cell
}
As you can see each tab has different custom cells with images.
Below are the problems I am facing
1) it takes time to reload data when I switch tabs and their is considerable lag time. On iphone 4s it is worse
2) When I open this page, first tab is selected by default, so when i scroll, everything works smoothly. But when i switch tabs, and when i scroll again after reloading of data, the scroll becomes jerky and immediately i get memory warning issue.
What I did so far?
1) I commented the image fetching code and checked whether that is causing jerky scroll, but its not.
2) I used time profiler, to check what is taking more time, and it points the "dequeueReusableCellWithIdentifier". So I dont know what is going wrong here.
Your code does not look "symmetric" with respect to cell set up when secodTabClicked and thirdTabClicked. I do not see firstTabClicked, and it looks to me that the condition that you are using to determine which tab is clicked overlaps with secodTabClicked and thirdTabClicked. In other words, you are probably getting into the top branch, and return EventTableViewCell when cell2TableViewCell or cell3TableViewCell are expected.
Refactoring your code to make type selection "symmetric" with respect to all three cell types should fix this problem.
Another solution could be making separate data sources for different tabs, and switching the data source instead of setting xyzTabClicked flags. You would end up with thee small functions in place of one big function, which should make your code easier to manage.
I am trying to implement a like feature in my app using parse. If a user taps the vote up button. The label increases changing the like number in parse side as well. However with my code a user can tap many times to increase the like. I would like to make it detect that user has tapped and make the like button disabled. To do that I have made a class in parse called "Liked". I made a username, imageId both a string column and a likeStatus as a Boolean . However I can't make is so that if a user likes any image it will add new item to it with userId, ImageId and likeStatus.
This is the Collection View 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 the country name
if let value = item["imageText"] as? String {
cell.postsLabel.text = value
}
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
cell.complition = {
self.likeButton(indexPath)
}
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
}
/*
==========================================================================================
Segue methods
==========================================================================================
*/
func likeButton(indexPath:NSIndexPath)
{
let cell = self.collectionView.cellForItemAtIndexPath(indexPath) as! NewCollectionViewCell
let object = self.votes[indexPath.row]
if let likes = object["votes"] as? Int
{
object["votes"] = likes + 1
object.saveInBackgroundWithBlock{ (success:Bool,error:NSError?) -> Void in
println("Data saved")
}
cell.votesLabel?.text = "\(likes + 1)"
}
else
{
object["votes"] = 1
object.saveInBackgroundWithBlock{ (success:Bool,error:NSError?) -> Void in
println("Data saved")
}
cell.votesLabel?.text = "1"
}
}
and this is the cell code
#IBAction func vote(sender: AnyObject) {
if self.complition != nil
{
self.complition!()
}
}
}
Any tips or How am I able to do this in code?Thank you.
The way I did this was by using a class in Parse that I called "UserLikeActivity" or something to that effect, and in it, it had a column pointer to the user that did the liking, a pointer to the actitivy that was liked (in my case it was a post), a type (indicating whether it was an upvote, downvote, follow, etc), and a pointer to the user who created the activity that was liked.
Now, when I was querying Parse to set my tables up, not only did I query the class that contained all the posts, but I also queried this class, which I then saved and used to determine the button state. So for every cell, if the activity had already been liked, I disabled the button. Hopefully this will help you get going in the right direction since you've asked this question about 7 times.
I have an optional image in the cloudkit DB(checked DB and the image is there in cases where I added it in my testing). I have created a class that initializes the record fields into variables I use in my tableview. I have a custom cell as well. But the image won't display in my custom tableview cell. I don't know if having an optional image in a tableview image is causing a problem or if there's an issue with my code/settings in cloudkit.
Any help is greatly appreciated, as I've been stuck for over a week and there's little to nothing online about this.
Here's my class code:
var feedResults = [UserPosts]()
class UserPosts {
var record: CKRecord
var description: String
var username: String
//var image: CKAsset? // tried also initializing the CKAsset separately
var postPic: UIImage?
init(record: CKRecord) {
self.record = record
self.description = record.objectForKey("description") as String
self.username = record.objectForKey("username") as String
// self.image = record.objectForKey("postPic") as? CKAsset
if var pic = record.objectForKey("postPic") as CKAsset! {
self.postPic = UIImage(contentsOfFile: pic.fileURL.path!)
// below is the other way I've seen it done.
// var url = pic.fileURL!
// var imageData = NSData(contentsOfFile: url.path!)
// self.postPic = UIImage(data: imageData!)
}
}
}
Here's my tableview code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
let record = feedResults[indexPath.row]
cell.postDescription.text = record.description
cell.userName.text = record.username
if record.postPic != nil {
cell.postPic.image = record.postPic!
}
return cell
}
Also I've seen a couple ways of pulling a CKAsset into a UIImage. The first way, is how I saw Apple do it in their CloudKitAtlas project. Although I'm not well versed in Obj-C, I think I followed it correctly - CloudKitAtlas Example
I've also seen it done using NSData(contentOFFile:) and UIImage(data:), as shown in this post here: SO CKAsset Example
Try doing it without the .paths and using contentsOfURL. This is how I do this (within your if for the CKAsset):
if var data = NSData(contentsOfURL: pic!.fileURL) {
self.postPic = UIImage(data: data!)!
}
And besides that... You do a dequeueReusableCellWithIdentifier but don't check if it returns nil. You should create a new cell instance If it was nil
You can display the image from the CKAsset like this:
var img = record.valueForKey("Picture") as? CKAsset
cell.imageView?.image = UIImage(contentsOfFile: img!.fileURL.path!)
Hope this helps!