I do not know if this is possible but, I am trying to return a cell using CollectionView cellForItem only if a certain criteria is met.
Here is my function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FriendsRecentCell", for: indexPath) as! FriendsRecentCell
var fObj = PFObject(className: FRIENDS_CLASS_NAME)
fObj = recentArray[indexPath.row]
// Get User Pointer
let userPointer = fObj[FRIENDS_IS_FRIEND_WITH] as! PFUser
userPointer.fetchIfNeededInBackground(block: { (user, error) in
if error == nil {
// Get heys sent to you as currentUser
let query = PFQuery(className: HEYS_CLASS_NAME)
query.whereKey(HEYS_RECEIVER, equalTo: PFUser.current()!)
query.whereKey(HEYS_SENDER, equalTo: userPointer)
query.countObjectsInBackground { (count, error) in
if count != 0 {
// Get User fullname
cell.userLabelRecent.text = "\(userPointer[USER_FULLNAME]!)"
// Get Avatar
cell.avatarImageRecent.image = UIImage(named: "avatar")
cell.avatarImageRecent.layer.cornerRadius = cell.avatarImageRecent.bounds.size.width/2
let imageFile = userPointer[USER_AVATAR] as? PFFile
imageFile?.getDataInBackground(block: { (imageData, error) in
if error == nil {
if let imageData = imageData {
cell.avatarImageRecent.image = UIImage(data:imageData)
}}})
} else {
}}
}})
return cell
}
I only want the cell to return if count != 0. Is there any way to go about doing this? I am completely lost.
At the point at which this method is called, you've already told the collection view that there's a cell that exists at that index path. Now that you've done so, you have to supply a cell; there's no way not to – the method signature is non-optional.
If you only want a cell to appear under certain circumstances, then you have to handle it earlier in the process. When collectionView(_:numberOfItemsInSection:) is called, you need to determine if the cell should be shown and return the correct number of items. Then you'll only be asked for those cells.
Related
For some reason, the array in my code can provide values stored in other positions but not in position 0 and 1, this array is inside a for loop and I'm just fetching images from the internet and appending to the array, but when I load these images into a collection view, all other cells but cell 0 and 1 display images with their index paths corresponding to the positions of the array, It shows in the array count that the total number of count in the array is the expected amount but then, it just won't get values stored in those positions.
class correctHomeViewController: UIViewController {
public var imgarrPic : [UIImage] = []
var imgarr = ["https://www.unsplash.com/photos/_3Q3tsJ01nc/download?force=true","https://www.unsplash.com/photos/dlxLGIy-2VU/download?force=true","https://www.unsplash.com/photos/TPUGbQmyVwE/download?force=true","https://unsplash.com/photos/9MRRM_hV4qA/download?force=true","https://unsplash.com/photos/HbAddptme1Q/download?force=true","https://unsplash.com/photos/wSct4rrBPWc/download?force=true","https://unsplash.com/photos/PKMvkg7vnUo/download?force=true"]
override func viewDidLoad() {
super.viewDidLoad()
loadImage()
}
func loadImage()
{
let config = URLSessionConfiguration.ephemeral
config.allowsCellularAccess = true
config.waitsForConnectivity = true
let sesh = URLSession(configuration: config)
for position in 0..<imgarr.count {
let url2 = URL(string: imgarr[position])
let data = sesh.dataTask(with: url2!) {data, _, error in
if data != nil && error == nil {
let data3 = data!
let image = UIImage(data: data3)!
self.imgarrPic.append(image)
} else {
if error != nil || data == nil {
DispatchQueue.main.async {
self.view.showToast(toastMessage: error!.localizedDescription, duration: 5)
}
}
}
}
data.resume()
}
}
}
//displaying in the collection view
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productCell2", for: indexPath) as! ProductsViewCell
DispatchQueue.main.async {
cell.cornerRadius()
if self.imgarrPic.count == self.imgarr.count {
cell.productImage.image = self.imgarrPic[indexPath.item]
} else {
cell.productImage.image = UIImage(named: "pwPic")
}
}
return cell
}
I'm making an app with a tableview and search controller using the rick and morty API, https://rickandmortyapi.com/api/character/. The API is paged and I'm able to parse the data from the first page and display it to my tableview. I'm also able get the other pages of the API when I scroll through the tableview. I can't seem to figure out how to parse all the data from the pages at once. When I use the search controller I can't search for all the characters, until I scroll through the tableView to get all of the characters. I want to be able to search for any character without having to scroll through the tableView first. How would I parse all the data from the different pages at once and display the data to the tableview? Any help is appreciated, thank you!
This is my current code for parsing the data and getting the other pages when scrolling through the tableView
func getIntitalRickAndMortyData(){
downloadedDataArray = []
//here first page is next page
nextPageUrl = "https://rickandmortyapi.com/api/character/"
getRickAndMortyData()
filteredCharacterArray = downloadedDataArray
}
func getRickAndMortyData() {
//construct the url, use guard to avoid nonoptional
guard let urlObj = URL(string: nextPageUrl) else
{ return }
//fetch data
URLSession.shared.dataTask(with: urlObj) {[weak self](data, response, error) in
//to avoid non optional in JSONDecoder
guard let data = data else { return }
do {
//decode object
let downloadedRickAndMorty = try JSONDecoder().decode(PagedCharacters.self, from: data)
self?.downloadedDataArray.append(contentsOf: downloadedRickAndMorty.results)
self?.nextPageUrl = downloadedRickAndMorty.info.next
self?.filteredCharacterArray = (self?.downloadedDataArray)!
self?.currentPage += 1
DispatchQueue.main.async {
self?.tableView.reloadData()
}
//print(self?.aryDownloadedData as Any)
} catch {
print(error)
}
}.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let count = self.downloadedDataArray.count
if count > 1 {
let lastElement = count - 1
if indexPath.row == lastElement {
//call get api for next page
getRickAndMortyData()
}
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: "rickandmortyCell") as? CharacterTableViewCell else { return UITableViewCell() }
let results: Results
if isFiltering() {
results = filteredCharacterArray[indexPath.row]
} else {
results = downloadedDataArray[indexPath.row]
}
cell.selectionStyle = .none
cell.nameLabel.text = results.name
cell.statusLabel.text = results.status
cell.genderLabel.text = results.gender
cell.originLabel.text = results.origin.name
cell.lastlocationLabel.text = results.location.name
let id = String(results.id)
cell.idLabel.text = id
return cell
}
}
I've tried doing it using a while loop and keeping a current page count and incrementing it, but nothing displays and I get this in my console "XPC connection interrupted"
func getAllRickAndMortyData() {
while currentPage <= 25 {
getRickAndMortyData()
}
}
firestore to store about more than 500 information and I want to display it to table view. Basically, I have successfully display all the data in my cell, but the problem is, it takes more than 1 minute to load all data. While the data loaded, I cannot scroll the table view, unless all data finish load. How to enable scrolling while the data is still loading? If not possible, how to load first 20 data first, and will continue load if user is at the end of the cell? Here is some code that I have tried to
get data from firestore:
func getData () {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
let data = document.data()
let agencyPath = data["agensi"] as? String ?? ""
let title = data["title"] as? String ?? ""
let program = data["program"] as? String ?? ""
let perniagaan = data["perniagaan"] as? String ?? ""
let newMax = data["max"] as? Int
let agencyId = document.documentID
let query = Firestore.firestore().collection("Agensi")
let newQuery = query.whereField("name", isEqualTo: "\(agencyPath)")
newQuery.getDocuments()
{
(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)");
} else
{
for document in querySnapshot!.documents {
let data = document.data()
let logo = data["logo"] as? String ?? ""
//store to Struct
let newModel = DisplayModel(agency: title, agencyId: agencyId, programTag: program, perniagaanTag: perniagaan, max: newMax, agencyPath: agencyPath, logoUrl: logo, agencyTitle: agencyPath)
self.agencyList.append(newModel)
}
self.tableView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}
}
}
}
}
display data on cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData: DisplayModel
if searchController.searchBar.text != "" {
cellData = filteredData[indexPath.row]
} else {
cellData = agencyList[indexPath.row]
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? HomeTableViewCell
cell?.agencyName.text = cellData.agency
cell?.agencyImage.sd_setImage(with: URL(string: "\(cellData.logoUrl ?? "")"), placeholderImage: UIImage(named: "no_pic_image"))
return cell!
}
Action on last row of cell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if searchController.searchBar.text != "" {
let lastElement = filteredData.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
} else {
let lastElement = agencyList.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
}
}
I really have no idea what method I should do to load 20 data first and continue load at the end of cell row, if there is no solution, at least I could scroll the table view during the load session. Thank You, for your information, i just learn swift last month. Thank you for helping me.
You should definitly adopt the UITableViewDataSourcePrefetching protocol.
Check some blogs, like:
https://www.raywenderlich.com/187041/uitableview-infinite-scrolling-tutorial
and adopt it to pagination as described here:
https://firebase.google.com/docs/firestore/query-data/query-cursors
I'm trying to get some strings and one photo from parse.com for tableview. I have an NSObject for this class and also an array of object to store them. I can get the newsTitle and the newsDetail in correct order by got fail when try to get newsPhoto. I suppose it lost its order when try get get images in block. Does anybody know what should I change on below code to fix it?
func getNews(){
let query = PFQuery(className: "bulletinOnParse")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock {
(allNews: [PFObject]?, error: NSError?) -> Void in
if error == nil {
var duyuru:News
for news in allNews! {
duyuru = News()
let nTitle = news.objectForKey("title") as! String
duyuru.newsTitle = nTitle
let nDetail = news.objectForKey("comment") as! String
duyuru.newsDetail = nDetail
let imageFile = news["newsphoto"] as! PFFile
imageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
duyuru.newsPhoto = image!
}
}
}
self.bulletin += [duyuru]
}
} else {
// Log details of the failure
print("\(error!.userInfo)")
}
self.tableView.reloadData()
}
}
And cellForRowAtIndexPath method below
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! DuyuruTableViewCell
self.tableView.rowHeight = 100
let cellInfo = bulletin[indexPath.row]
cell.newsTitle.text = cellInfo.newsTitle
cell.news.text = cellInfo.newsDetail
dispatch_async(dispatch_get_main_queue(), {
cell.newsPhoto.image = cellInfo.newsPhoto
})
return cell
}
Here is the answer of how I solve the problem;
First I created an image array from PFFile object
var resultUserImageFiles = [PFFile]()
Then I get the name and add the array on getNews() method
self.resultUserImageFiles.append(news.objectForKey("newsphoto") as! PFFile)
And I get each photo for cell on below method in cellForRowAtIndexPath method.
self.resultUserImageFiles[indexPath.row].getDataInBackgroundWithBlock { (imageData: NSData?, error:NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
cell.newsPhoto.image = image
}
}
I am trying to get a friend list from Facebook, i have the call request inside the tableview cellforrowatindexpath but i also have it in a separate function too. Thought to try both ways but i get the same result.
If i do a print from inside the completion block i do get results, however, if i try to print friendProfile outside the completion block it returns nil and therefore when i create the table cells they unwrap nil and the code fails. I think it is because the completion block is not returning data until after i call to create the cells. I am reading, and perhaps this has to do with Asynchronous programming? Or needing to wait till data is returned from Facebook? Or will the completion handler always return nil?
`
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var request = FBSDKGraphRequest(graphPath:"/me/taggable_friends", parameters: ["fields":"user_id,first_name,last_name,name,picture.type(large)"]);
request.startWithCompletionHandler ({ (connection : FBSDKGraphRequestConnection!, result : AnyObject!, error : NSError!) -> Void in
if error == nil {
//print("Friends are : \(result)")
let jsonResult = result
self.friendsArray = jsonResult["data"] as! NSArray
var index = 0
for data in self.friendsArray {
let fn = data["first_name"]! as! String
let ln = data["last_name"]! as! String
let id = data["id"]! as! String
let picture = data["picture"] as! NSDictionary
let parsePic = picture["data"] as! NSDictionary
let url = parsePic["url"] as! String
self.friendProfile .updateValue((fn + " " + ln), forKey: id)
self.friendProfilePic .updateValue(url, forKey: id)
self.friendProfileID . updateValue(id, forKey: index)
index++
}
} else {
print("Error Getting Friends \(error)");
}
//if i do a print(friendprofilId) in here it works
})
//but a print(friendprofilId) here returns nil
let cellidentifier = "SettingsTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellidentifier, forIndexPath: indexPath) as! SettingsTableViewCell
let id = self.friendProfileID[indexPath.row]
print(self.friendProfileID)
if (id != nil) {
let name = self.friendProfile[id!]
let url = self.friendProfilePic[id!]
// cell.imageView?.image = returnFriendProfileImage(url!)
cell.textLabel!.text = name
}
return cell
}
`
You need to call tableView.reloadData() in the place where you have it commented that it works. Also, don't retrieve your data in cellForRowAtIndexPath. Just call the function you had in viewDidLoad
An asynchronous function call with return immediately, while the completion block will complete once it receives a response from the web server. In your case, you call startWithCompletionHandler, which returns immediately and configure your cell before the completion block receives a response from Facebook's server. It makes sense that friendProfilId is still null since you haven't received the data from Facebook.