Number of item count into UICollectionView - ios

I am trying to display some data in the collection view. Data will be coming from the server. I am able to get and show the data in collection view when I gave the number of items as static like return 20. Whenever I tried to display data from a server like return array.count, that time I am not able to display the data. I just simply got the array of data from the server and added that array to globally declared array, in the number of items section I have given return globallydeclaredarrayobj.count. Can anyone helps me, would be great? Thank in advance.
//Globally declared variable
var pro = [[String:Any]]()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pro.count
// return 20
}
//Data from server
var productsdetails = [[String:Any]]()
productsdetails = userdata.value(forKey: "products") as! [[String:Any]]
self.pro = productsdetails
print("result\(self.pro)")
self.collectionview.reloadData()

Try to print the count before you returning the count in
print(Pro.count)
retrurn Pro.count

Related

How to prevent current user's profile from being displayed in a collectionView using Firebase

I have a View Controller in my app that displays a profile of all users (their profile pics, names, and occupation) through a UICollectionView. I'm trying to get the app to not display a profile of the current user (for obvious reasons), but I'm struggling to figure out how to do this.
I'm using Firebase to fetch users' data, so I have no issue accessing the current user's info. I'm just not sure how I would define the logic for this and where it would be defined. Would it be defined under the collectionView function numberofItemsInSection or cellForItemAt? Below is what I have in those functions currently.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return users.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SearchProfileCell", for: indexPath) as! SearchProfileCell
cell.delegate = self
cell.user = users[indexPath.item]
return cell
}
Do it in your datasource for the collectionview. When you build your data source, compare the Auth.auth().currentUser?.uid to the uid of the users you're adding. If they match, don't add that record to the datasource. That way, you don't have to change the other functions.
One approach could be to filter out the current user from your users array before loading the CollectionView. Let's say you're fetching the users and storing them in your array users of type [[String: String]] (not sure, i'm guessing since you didn't provide a full code). Then you can create a struct like this:
struct FilteredUser {
let name: String
let occupation: String
let email: String
// etc...
}
Note that you have to keep a unique ID for each user, in this example it's the email (there are better options like uid tho). Also, somewhere you have to keep track of the current user unique ID, for example you could store the current user email in UserDefaults (if you use uid it's even simpler). Now you can filter the array like this:
let filteredUsers: [FilteredUser] = users.filter({
guard let email = $0["email"], email != UserDefaults.standard.string(forKey: "currentUserEmail") else {
return false
}
// filter by other values like name, occupation, ... if needed
}).compactMap({
guard let email = $0["email"],
let name = $0["name"], let occupation = $0["occupation"] else {
return nil
}
return FilteredUser(name: name, occupation: occupation, email: email)
})
Note that you then need to provide filteredUsers in your dataSource.

.reloadData() without reCreating visible cells or those who are in memory?

When I have instantiated the third cell, I will add more to my items to my model array and then I will update the collection view data with:
DispatchQueue.main.async(execute: {
self.collectionView.reloadData()
})
Everything works as expected. However, when I reload the data for my collectionView, it will instantiate cells that are currently visible or hold in memory (2,3). Unfortunately, I have some expensive server requests which consume a lot of time.
Instaniated 0
Instaniated 2
Instaniated 3
******polluting more data: size of cells 10
Instaniated 2
Instaniated 3
Instaniated 4
How can I reload the data without reCreating visible cells or those who are in memory?
Thanks a lot.
Instead of reloading the cells, try inserting or reloading the once that have actually changed. You can use UIColletionViews performBatchUpdates(_:) for this: Link
An example:
collectionView.performBatchUpdates {
self.collectionView.insertItems(at: [IndexPath(row: 1, section: 1)])
}
This ensures that only the new cells are loaded. You can also move around cells and sections in this method and even delete cells. The linked page contains documentation for all of this.
Why can't you go with below approach
1) I hope you have declared dataSource and collectionView objects as global to the class
let collectionView = UICollectionView()
var dataSource = [Any]()
2) Have one function to get the initial results from the API response
func getInitialPosts(){
// call api
// store your initial response in the local array object and reload collectionview
let results:[Any] = {response from the server call}
self.dataSource.append(contentsOf: results)
DispatchQueue.main.async(execute: {
self.collectionView.reloadData()
})
}
3) For the next call, you can have another function
func getPostsForPage(page:Int){
// call api with page number
let newResults = {response from the server call}
self.dataSource.append(contentsOf: newResults)
var indexPathsToReload = [IndexPath]()
let section = 0
var row = self.dataSource.count - 1
//add new data from server response
for _ in newResults {
let indexPath = IndexPath(row: row, section: section)
row+=1
indexPathsToReload.append(indexPath)
}
// perform reload action
DispatchQueue.main.async(execute: {
self.collectionView.insertItems(at: indexPathsToReload)
})
}
Suppose from your network adapter you are calling delegate function fetchData with new data. Here you have to check if your data is empty or not to check if you need to add new data, or reload entire CollectionView.
Then you create all indexPaths that you need to fetch more, in order to let already fetched cell stay as they are. And finally use insertItems(at: IndexPaths).
I use page in order to paginate new data with page number in the future. Strictly for my use case. Good luck!
func fetchData(with videos: [VideoModel], page: Int) {
guard self.data.count == 0 else{
self.addNewData(with: videos, page: page)
return
}
self.data = videos
DispatchQueue.main.async {
self.isPaging = false
self.collectionView?.reloadData()
self.page = page
}
}
func addNewData(with videos: [VideoModel], page: Int){
var indexPathsToReload = [IndexPath]()
let section = 0
var row = self.data.count - 1
self.data += videos
for _ in videos {
print(row)
let indexPath = IndexPath(row: row, section: section)
row+=1
indexPathsToReload.append(indexPath)
}
DispatchQueue.main.async{
self.isPaging = false
self.collectionView!.insertItems(at: indexPathsToReload)
self.page = page
}
}

Swift: Copying oldArray[][] to newArray[][] causes error (Type 'Any' has no subscript member)

Using Swift for my collectionView app. I'm reading a plist file to display the info in the arrays.
This is the code to read the plist which creates an array called sections[section #][item #] which lets me access items inside the arrays of the root.
var Items = [Any]()
let url = Bundle.main.url(forResource:"items", withExtension: "plist")!
do {
let data = try Data(contentsOf:url)
let sections = try PropertyListSerialization.propertyList(from: data, format: nil) as! [[Any]]
for (index, section) in sections.enumerated() {
//print("section ", index)
for item in section {
//print(item)
}
}
print("specific item: - \(sections[1][0])") // (Section[] Item[])
print("section count: - \(sections.count)")
Items = sections
} catch {
print("This error must never occur", error)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch checkGroupChoice {
case 0:
print("From: Items")
print("specific item 2: - \(Items[0][1])")
return Items.count
I created var Items = [Any]() to transfer everything from sections[][] to the array Items so I can use it as a global array for my collectionsView but I get an error.
Type 'Any' has no subscript members
on the line print("specific item 2: - \(Items[0][1])")
How do I successfully transfer sections[][] to Items? I'm sure I'm not creating the array correctly. Thank you
Items is a ONE dimensional array.. you are trying to index it as a 2D array.. Change it to:
var Items = [[Any]]()
Now you can assign to it and append to it and index it as a 2D array. Each dimension requires a matching set of square brackets..
Example:
1D array: [Any]()
2D array: [[Any]]()
3D array: [[[Any]]]()

IOS Swift reading data from a dictionary of [String: [AnotherKindOfDictionary] ] ( )

I would like to read data from a dictionary that contains dictionaries of images (or any sort of object really). Each dictionary has a key (the String).
For a somewhat visual understanding this is what I am trying to achieve:
userIdOne -> [image1, image2, image3, image4]
userIdTwo -> [image1, image2, image3]
userIdThree -> [image1, image2, image3, image4, image5]
userIdFour -> [image1, image2]
NOTE: these images are not the same image despite having the same "title". They just belong to each individual user. The userId is the [String:... and the dictionary of images is the [AnotherKindOfDictionary] I mentioned in the title of this question. I want each userId and their images in each cell. So in total, this would show 4 cells, BUT when tapped, their images would show in sequential order.
The problem is that I want to put this data in a UITableView or UICollectionView. I've worked with both before so whichever works.
Something similar to how snapchat works. Whenever a cell is tapped, the images from that user are shown sequentially.
I've been able to load the data into the dictionary with each userID being the key but I am having trouble using the data in a collectionView(my current choice, although I can use a tableView)
Here is my code:
var stories = [String : [StoryMedia]]()
// StoryMedia is a struct containing info
struct StoryMedia {
var storyMediaId: String?
var creatorId: String?
var datePosted: String?
var imageUrl: String?
init(storyMediaKey: String, dict: Dictionary<String, AnyObject>) {
storyMediaId = storyMediaKey
creatorId = dict["creatorId"] as? String
datePosted = dict["dateposted"] as? String
imageUrl = dict["imageUrl"] as? String
}
}
... Now in the actual viewController class UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return stories.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let storyCell: StoryCell!
storyCell = collectionView.dequeueReusableCell(withReuseIdentifier: storyReuseIdentifier, for: indexPath) as! StoryCell
// What should I do here?
return storyCell
}
The problem lies with trying to setup each cell. I cannot pull each dictionary value by its key and use it for each cell.
I've tried using:
// Failed attempt 1)
let story = stories[indexPath.row]
// but I get an ambiguous reference to member 'subscript' error
// Failed attempt 2)
for story in stories {
let creatorId = story.key
let sequenceOfStoryItems = story.value
for singleStoryItem in sequenceOfStoryItems {
// do something...
}
}
// but looping through an array for a collection view cell
// does nothing to display the data and if I were to guess,
// would be detrimental to memory if
// I had a lot of "friends" or users in my "timeline"
A dictionary isn't ordered, so it's awkward to use that for the cellForItem function (and why you can't use that subscript). You might be able to use the dictionary's values (i.e. ignore the keys), but that could be different b/n runs. What I mean is, you can use the subscript on the stories.values array (don't remember the exact call for "values", but it's close...allValues?...not sure, don't have a way to double check right now) instead of the stories dictionary.
Does var stories = [String : [StoryMedia]]() need to be a Dictionary?
Dictionaries aren't ordered, so can't index them like you are asking. Can you make it an array of tuples? var stories = [(username:String, media:StoryMedia)]() Then you can add whatever value you were planning to store in the original key into the username: field on the tuple. You could make another struct that has username and media properties if you prefer over the tuple.
It should be trivial to pull individual username or media structs out of the array with a simple stories.filter{} call.

Making unordered items in an array to always in same order

My app has a VC with tableview which contains 10 cells(questions). User selects whatever they apply to them and press "next" button. Then, it goes to next VC has a table view and initialize corresponding section to the questions.
For example, if I select "Question1, Question 2" out of 10 questions, then it should make two sections which have titles of "Question1" and "Question 2".
Right now I add a question selected in an array like following:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
result.append(allCriteria[indexPath.row])
}
Then, when "next" button is pressed, it passes result array to next VC. Based on the array "result" is passed, it initializes sections in order.
The problem is that it changes the order of section if I press the questions in random order. And this is expected.
Would there be anyway solve this?
In short, even if a user selects questions in order of "Question2, Question1", I would like sections to be made in order of "Question1, Question2"
result array is type of [Question]! and Question class is like below:
class Question {
var documents: [Document] = []
var title: String
var description: String
init(dict: [String:AnyObject] ) {
// parse dict to generate a bunch of documents, add them to self.documents
self.title = dict["Title"] as! String
self.description = dict["Description"] as! String
let documentsDicts = dict["Documents"] as! [Dictionary<String,AnyObject>]
for dictionary in documentsDicts {
let document = Document(dict: dictionary)
self.documents.append(document)
}
}
}
Instead of adding the Question to the result array when user selects a cell, you can just add the indexPath of that cell to an array. When user clicks next, you just sort that indexPath array by ascending order, then based on that, you just generate the result array and pass it to the next view controller, something like:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
indexPathArray.append(indexPath)
}
func next() {
let sorted = indexPathArray.sort(<)
let result = sorted.map {
(var index) -> Question in
return allCriteria[index]
}
// ........
}
Reading from here, a solution can be to sort your array alphabetically by question's title and do something like this:
result.sort { $0.title < $1.title }
In Swift 3
self.resultArry.sort(by: { $0.title < $1.title })

Resources