Pre-requisites - Environment: iOS 9.0 or above - using Swift 3.0.1
Thanks for your responses. I'm updating the question and trying to give a better understanding about the problem.
Posting code would help may be but I'm not allowed to post the code as I do not have the IP.
But I am trying to build something like calendar/program guide where you have events for each category for several days.
Imagine, categories on your left side in a column and they can be the sections of the collectionveiw and each category has events for several days which is a row.
CAT 1 : Event 1, Event 2 ... Event n
CAT 2 : Event 1, Event 2 ... Event n
CAT 3 : Event 1, Event 2 ... Event n
.
.
.
CAT m : Event 1, Event 2 ... Event n
Problem: The entire data is pretty dynamic and humongous. I can't prefetch all the records, they are about over 80-100K. It takes few minutes to download all the data and display it on the grid.
A user could select any day and any time and I have to scroll the collection view to that day and time and display those events for the categories. Also, user could obviously scroll in both directions to and browse the events in this case the events are loaded like infinite scroll fashion.
In the former option though, when the user jumps on to a particular day and time on the entire timeline and I have to skip loading the other previous events (as I do not have them yet - unknown) and display the events relevant to the user selected days and time.
I do not have all the IndexPaths in advance, to display on the screen, how can I skip events and dynamically update the collection view in parts like we load images dynamically and the ones which get loaded first and displayed earlier than others.
I'm using startDate of the events to calculate the xPosition, categories don't change often after they are loaded so we could somehow avoid reloading sections but items in those sections change all the time and they appear in a random fashion.
When the controller loads the first set of events are fetched from the server and displayed, now if the user decided to jump to some D-Day and T-Time which could be anywhere on the entire timeline I have to fetch the events for those dates and populate the items for relevant sections (visible on screen) and update the interface. This is where I have issues, where I do not have an proper approach.
Hope this is clearer.
I have "tried" to mock this up
UICollectionViewFlowLayout can help you achieve what you want...
https://developer.apple.com/reference/uikit/uicollectionviewflowlayout
https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
You have the same problem I had with my calendar project. The solution I have implemented will not work for you, but I am mentioning it here so that it might give you clues on how to solve it for your situation.
My calendar has a function where a user can scroll to some date way into the future. The problem was that date cells can be custom sizes. Therefore, since they are scrolling to some future date, in order for me to know the destination offset, I needed to know the offsets of cells 0 -to- destinationOffset because the cell sizes are different. This meant I had to query the sizes of all the cells in the middle which led to a 2-3 lag time (or in your case, a long download time).
So here was my solution.
I originally had a delegate function called sizeForCellsAtMonth which was called for every month in order to determine the size. I have now changed this function to be called only once.
The function now only has two parameters:
defaultSizeOfCells
exceptionToDefaults - this will be specific months where the cell sizes are different
Using this information, I can calculate the sizes of all months because I know the sizes before hand. So my problem was solved by changing the way I looked at my delegate. Maybe you can try looking somewhere along those lines or maybe my answer gave you clues of what you can do.
Related
I have a database that holds Posts. At a future point, my database may hold a very large number of Posts. I am confused as to what to do, as I cannot decide between the few options that I've thought of so far:
1) Load all Posts at once and store them into an Posts[] array, and just show all posts on the TableView that displays them.
2) Load 10 Posts at once and display those 10 at a time, then implement a function that allows the user to scroll and to load 10 more at a time. These 10 values that are loaded will then be added to the TableView.
Now, option #1 seems simple and attractive, as it is what I currently have set up. However, I am not sure if it would be problematic or not to constantly load hundreds of posts every time a user opens the page that displays Posts.
Option #2 seems complex, as I have no idea how to only load 10 Posts at once using Firebase. I am using a .childAdded observation to gather the data, and that usually loads all of the Posts. An alternative idea I had that may or may not be useless is loading all Posts into the Posts[] array but only displaying 10 at a time. This option is attractive because users won't have to load every single post every time they view the TableView that contains the posts. I am also hesitant to take this option because I would have to alter my data structure quite a lot. The current set up is:
root/posts/post-id/post-info
In which the post-info node holds information relevant to the post, and does not contain an index, which I have a feeling that option #2 would require.
I'm quite stuck here. What's the best action to take in a situation like this?
I was having the same problem creating a "forum" like app. There were a lot of threads and loading them all at once was not the right approach.
If I were you I would stick to method 2: Load X number of posts, when TableView is scrolled to the bottom (or almost to the bottom) load additional posts and display them.
Here is a quick example:
You need a variable which will hold information on how much posts should be displayed:
var numberOfPosts: Int = 20
Query your posts using queryLimited:
reference.child("child").child("child").queryLimited(toLast: UInt(numberOfPosts))
This will return last X posts. You should also order it by some key to make sure you really have latest posts in the right order:
reference.child("child").child("child").queryOrdered(byChild:"timestamp").queryLimited(toLast: UInt(numberOfPosts))
So the idea is, first time it will load 20 posts. The next time it should load 40, etc. That is why we need to implement something to recognise that all the posts were displayed and we need to load new ones. I am doing that with scrollView function - each time I am almost reaching the bottom, additional posts load. Implement scrollView didScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y + 100) >= (scrollView.contentSize.height - scrollView.frame.size.height) {
// You have reeached bottom (well not really, but you have reached 100px less)
// Increase post limit and read posts
numberOfPosts += 20
readPosts()
}
}
If you have UITableViewDelegate in the same class, function should be working by itself since TableView uses scrollView delegate.
So this should be enough to get you going. I don't have the actual code right here so can't really paste it to help you.
By the way, I am using .value and not .childAdded but you should be able to implement something like this anyway.
In our iOS application, we are using core data and tied it with a table view using NSFetchedResultsController. The app is about "Chat" feature.
UI is same as that of iPhone "Messages" app. When we tap on a message, it displays the history and all the history grouped with time. The logic behind it is, if previous message and current message are received with a gap of 1 hr, then date & time stamp will be displayed over recent message.
My question is, how can I group the messages and fetch them so that I can show the date & time stamp as well as sender and receiver messages.
There are four types of message cells type - regular, group-start, group-middle, group-end. A group-start message is more than a hour after the last one but less than an hour to the next one. group-middle is less than an hour from the one before and after. group-end is close to the one before it, but more than hour to the one after it. regular is more than a hour before and after it.
There are two parts of this project. One is to display each type of cell correctly. The other is figure-out which type each message is. I assume you can figure out the UI stuff yourself (different padding, for each one, regular and group-start show the time, not rounding some corners, etc).
For each message to figure out its type, is not that hard - just look at the message before it and after it. It can be done in a single run through of the results - O(n). It could also be done lazily with a cache (ie each time a cell load check the message before and after it - save the answer in the cache for next time). If the cell sizes are different for different types then it make cause some weird jumping with estimatedRowHeight. You could also store the results of the type into core-data after you calculate it.
Be careful when a message is inserted to invalidate and recalculate the message cell type for the one above and below it. Also when calculating the message cell type account for situations where there isn't a next or previous cell.
I think you were hoping for some core-data magic - like some cleaver trick with sectionIndexKey. But it is really much more straight forward of just running through the array and calculating it.
Update:
Just to make it clear: don't use sections. Keep all the cells in one section. Just add the time to the top of the cell for the cell type group-start. It is a lot easier than dealing with sections especially when there are inserts that can cause and earlier cell to change from normal to group-start.
So the point is that I would like to have a viewcontroller for every day of the year in my custom navigation bar menu. I am using PagingMenuController library as my menu. There does not have to be 365 menu tabs, the interval can be like 90 days and every time the day changes it deletes the first day from the array and creates new one as last day of the array.
Basically is there a way to achieve it any way painlessly?
The app called ClutchPoints has this feature and I would like to get the exact same result.
This might be too much but I really hope there are some generous people out there who can help me.
Don't create 365 (or even 90) views at a time. You will have serious memory/performance problems if you do that.
You should look at the way table views, collection views, and UIPageViewControllers work. Those all only create the small number of views/view controllers that are actually visible at any one time, and configure the visible ones based on a data source that has the array of data that is being displayed. The views are recycled in order to manage memory.
How many cells to commence with in a UICollectionView for a Calendar? That is my understanding is:
UICollectionView is good in that it only instantiates cells it
needs to display, but then
You still have to add the cells to the collection view
So what is best practice if say your view would only show 10 cells, but you had unlimited cells (e.g. scrolling up and down dates in a calendar). Do you enter say 100 cells for 100 dates (say 50 either side of the starting date of interest) and then manually keep track of when you get to one of these edges and then add more? This would kind of be ashame the UICollectionView framework couldn't keep track of this itself no?
So overall questions therefore (sorry) are really:
How many cells (dates) should you be creating in the collectionview. Should it be limited therefore to say 100
Is there no support in UICollectionView to automate adding more cells in for you for a Calendar type situation where dates are endless if the user wants to keep scrolling forward or backward
Any what is impact on the caching strategy for UICollectionViewLayoutAttributes too?
There is a project on Github with a calendar based on UICollectionView
https://www.cocoacontrols.com/controls/rsdayflow
I have used this component in a production project.
You can easily change it's appearance since every day is a uicollectionviewcell.
This project is based on https://github.com/evadne/DayFlow
that can be a good base to create what you need.
If you want to build your own calendar it can help you to find response to your questions: the code is easy to understand, and the implementation is in my opinion quite neat
It depends on how much is visualized in one screen. If it is one month i would have one month before and one month later. If it is only a day, one day before, one day later. Then use this infinite scrolling trick. There are many examples outside. I didn't read through the following link, but the pictures visualize the idea nicely:
Building a Infinitely-Scrolling Gallery With a UICollectionView
I'm working on an a game app in swift that currently has a tableView displaying the scores, number of trys etc, downloaded in an array from parse.
However, this table can get pretty long if the user plays the game many times. So I'd like to improve the app by displaying the first, say, 20 objects of the array in a tableview, then, once the user scrolls down to the end of the table it automatically adds more rows and displays the next 20 objects in the array (along with the original 20, making the tableview now 40 rows)
If anybody's familiar with the twitter app, that's exactly what I'd like to go for. There's a set amount of tweets shown initially, then once you scroll down to the end of the table more tweets are loaded, in order to decrease loading times.
Problem is, I really have no clue how to implement this at all. I've never been in the situation where I only need to use part of an array. Any help would be greatly appreciated. Dan
UITableView is a virtual view where it calls you back for the data to create cells for a given row that's in view and destroy's cells that go out of view. You should also re-use cells that have been created.
So, I think the answer to your question about pre-loading portions of the list is ... you shouldn't have to. Just implement the data source and answer the data call backs.
More here in the apple docs on the datasource for a UITableView
This is addressed in How to know when UITableView did scroll to bottom in iPhone in Objective-C. If any help needed translating this to Swift, post as a comment and I'll update answer.
EDIT -- The real question:
The tableView delegate methods allow you to perform arbitrary logic / mapping between your data and the table's viewable data. So when you do detect a scroll to the bottom, increment an internal state variable, such as var rowsToReveal declared as a class-wide stored property. After incrementing, call tableView.reloadData(). Then re-write numberOfRowsInSection delegate method to use rowsToReveal rather than someDataArray.count or whatever hardcoded value you had. Make sure rowsToReveal never exceeds the count, of course, otherwise -- instant crash.