How to reset UICollectionView in Swift - ios

I am developing a card game using UICollectionView. I override the draw() function of the UICollectionViewCell to arrange my images when load the cells. Every new game, i change the number of cards in the game. So the number and size of the cells change. I have to do all operations for UICollectionView in the viewWillAppear() method because of the third part library i use. My question is that how to reset all the works which done over the UICollectionView before. I want clear UICollectionView during each game without loading UIViewController again. Wanna do it in viewWillAppear() method. I want to clear UICollectionView because my images overlap if don't have clear UICollectionView in new game.
Note : I removed all the subviews from UICollectionView like below, but it didn't work
let subViews = gameCollectionView.subviews
if subViews != nil {
for view in subViews {
view.removeFromSuperview()
}
}

Clearing the collection is from clearing it's dataSource array like
arr = []
gameCollectionView.reloadData()
if you need to clear the cell then do it inside cellForRowAt
let cell = ///
cell.contentView.subviews//// clear here
or override
override func prepareForReuse() {
super.prepareForReuse()
// clear any subview here
}
if you correctly assign values for all properties of the collection cell every run of cellForRowAt without adding subviews , then you'll have no overlappings

You need to remove every subview from each cell's contentView, not from your collection view
gameCollectionView.visibleCells.forEach { $0.contentView.subviews.forEach { $0.removeFromSuperview() } }
But, I'm not sure for what you need to override draw method. You should rather create what you need just once, for example in awakeFromNib and then you should just change properties of your image views, etc. depending on content, for this you purpose you can use collection view's data source method cellForItemAt.

Related

How to properly manage memory while using child view controllers in UICollectionView cells?

I have UIViewController(HomeController) with horizontally scrollable UICollectionView in it. This collection view contains 2 cells (the number is not dynamic, there will be always only 2 cells). The cell covers all the screen. In each cell I have a child view controller, which has a UICollectionView itself and scrolls vertically. The cells of this inner collectonView contain big images (cover whole size of a cell).
I set up my child view controllers in HomeController's viewDidLoad like this:
addChild(viewController)
viewController.didMove(toParent: self)
To add child controller's view as a subview to my cells I have hostedView property in the cell's class:
class ContainerCell: UICollectionViewCell {
var hostedView: UIView? {
didSet {
guard let hostedView = hostedView else {
return
}
contentView.addSubview(hostedView)
hostedView.translatesAutoresizingMaskIntoConstraints = false
hostedView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
hostedView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
hostedView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
hostedView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
}
}
}
This hostedView of the cell I set up in cellForItemAt method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = containerView.dequeueReusableCell(withReuseIdentifier: ContainerCell.reuseIdentifier, for: indexPath) as! ContainerCell
cell.hostedView = childControllers[indexPath.item].view
return cell
}
childControllers is an array where I store my two child view controllers.
The problem there is when app is launched for the first time and I scroll from the first cell to the second there is a delay in UI's reaction to the scroll. It scrolls not immediately but about 0.5 sec later after I do a scroll gesture. After that back and forth scrolls work fine. As far as I can tell this happens because at the launch the 2nd cell is not visible and cellForItemAt method for it hasn't been run yet. It runs when I scroll to this cell. This is completely fine behavior driven by iOS' reuse mechanism. But seems like because there is child controller in the cell with big images in it there is too much memory needed to be allocated so it causes this delay. At the launch Xcode shows about 100mb in memory, after a scroll to the second cell it grows to almost 200mb.
So even that I initialize all child controllers at viewDidLoad, the memory for them allocates only at cellForItemAt stage.
I would like to allocate that memory at the initial stage, so when I scroll they would be in the memory already. It kind of goes against cell reuse mechanism, but I know that there will always be only two cell (and two child controllers), so I don't really need that dynamic style behavior from my collectionView.
I tried to use different reuseIdentifiers for cells, and assign child controller's view to cell's hostedView in viewDidLoad, not in cellForItemAt, but it hasn't worked.
Is there any way to do that? And if there is not, how to approach this another way? This delay in UI reaction is really ruining user experience of the app.

iOS UICollectionView with horizontal scrolling inside vertical UICollectionView cells reusing

I have a performance issue when scrolling vertical collection cells with horizontal collection view in it. The reason is cell's data reusing - horizontal collection view reloading on every parent cell reuse cycle. Does anybody know how can I avoid such amount of reloads ?
var items: [ProductItem] {
didSet {
collectionView.reloadData()
}
}
override func prepareForReuse() {
super.prepareForReuse()
items = nil
}
UICollectionView and UITableView use every cell again and again, that is how they work.
It you get your data from web ever time then cache them. You can use image-caching libraries.

how to hide the image/label in StoryBoard to show up when we run the app?

I am making a table view app which retrieves the data from the Firebase. when making the user interface in the storyboard, I am using dummy image and label to visualize my app.
but when i run the app which consists of dynamic table view, those dummy images and label also shows up before immediately replaced by the actual data that i download from the Firebase storage.
can I set those images and labels to not show up when i run the app but still available in the storyboard?
thanks in advance :)
If they're just dummies, you can get rid of them when your view loads, before it appears onscreen:
override func viewDidLoad() -> Void{
super.viewDidLoad()
dummy.removeFromSuperview()
}
Whenever you want to hide/show an UIView:
myView.isHidden = true // hide
myView.isHidden = false // show
I assume what you need is to hide the views in viewWillAppear and then show them when necessary.
In your custom cell class, define something to hide the unwanted views:
func hideDummyViews() {
// do some stuff to hide what you don't want, e.g.
myEnclosingStackView.isHidden = true
}
In your table view controller, in the cellForRowAt indexPath func:
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell ..
if yourDataSource[indexPath.row].isCompletelyLoaded {
// do your fancy dynamic cell layout
} else {
// show the basic version (minus any dummy views)
cell.hideDummyViews()
}
return cell
You can choose your preferred method for hiding the items (isHidden for each view, removing, adjusting constraints). I prefer to embed any disappearing views in a stack view and then use isHidden = true on the enclosing stack. This keeps things organized in your storyboard/XIB file and neatly recalculates constraints for the hidden stacks.
It seems that you want to show some empty (or incomplete) cells until database content arrives and then you will reload each cell as you process new entries in the datasource. This answer will initially give you a set of cells appearing as per your storyboard/XIB, minus the hidden dummy elements. Then as items in your datasource are loaded fully, you can reload the cells.
By the way, it seems like a lot of work to carefully layout these dummy views for "visualization" and then never show them in the app. Why not have some user-friendly place holders or progress indicators showing and then animate in the real/dynamic views as the data arrives?
I assume you download the Image from FireBase and does't want the dummy Image to appear
(Why dont you declare an empty ImageView and an empty Label!). Try setting an Image array in the viewController. Or you can use a struct array if you want a Image and label text together.
var arrImage=[UIImage]()
var arrLblTxt=[String]()
In view did load append your Demo Image's if required.
arrImage.append(UIImage("Demo"))
arrLblTxt.append("Demo")
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tbl_AssignEmployees.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as YourCell
if arrImage[IndexPath.row] != UIImage("Demo")
{
cell.ImageView.Image = arrImage[indexPath.row]
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
else
{
cell.ImageView.Image = nil
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
Then After you download your Image From FireBase,
Reset your Image array with the new Images downloaded from fireBase
Then reload your tableView. tableView.reloadData

UITextView in a UITableViewCell - Height resizes correctly but cell is cut off

The Problem
I have a UITextView inside of a custom UITableViewCell subclass that is producing some odd behavior.
Here is a gif of the problem I'm seeing (relevant items are colored):
The height resizes correctly, but the full contents of the cell are not shown.
But when I pause execution and print out frames of the following:
Cell
Cell's content view
Text View
the frames are all correct!
Further, when I inspect the view using the view hierarchy debugger, the frames are all correct there too. There is one difference when using the view debugger though, and that is that I'm able to view the contents of the text view.
Here is what I see on the simulator vs in the debugger:
There seems to be an extraneous cell separator at the point where the yellow stops. Other than that, I can't find any sort of indicator of why the cell is not expanding past its original height.
My Code
In the storyboard, the UITextView has top, bottom and trailing constraints to the cell's content view, and a vertical spacing constraint to the UIImageView. The UIImageView has a fixed width and has a leading constraint to the cell's content view. I believe my constraints are set up correctly. Oh yeah, and scrolling is disabled on the UITextView.
As for code, I have a custom protocol that informs the table view controller when the notes field has changed:
protocol AddContactCellDelegate {
func notesViewDidChange(textView: UITextView)
}
Here is the relevant code from my UITableViewCell subclass:
class AddContactCell: UITableViewCell, UITextViewDelegate {
var delegate: AddContactCellDelegate?
func textViewDidChange(textView: UITextView) {
delegate?.notesViewDidChange(textView)
}
}
and from my UITableViewController subclass:
class AddContactViewController: UITableViewController, AddContactCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 60.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = rows[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(row.cellIdentifier, forIndexPath: indexPath) as! AddContactCell
cell.configureForRow(row)
cell.delegate = self
return cell
}
func notesViewDidChange(textView: UITextView) {
tableView.beginUpdates()
tableView.endUpdates()
}
}
Discussion
I've tried adding setNeedsLayout(), in to either the cell, the cell's content view, the tableview, or the textview, in just about every place, to no avail.
I rebuilt the entire view in the storyboard from scratch, same thing.
I tried creating a bare-bones project that has basically only the code above, and the sizing works correctly.
I've ruled out tons of other variables as being the culprit (for example, the tableview is in a container view, but the behavior persists without that).
One final weird point is that sometimes if I scroll the notes cell off the screen, leave the page, and come back again, everything looks as it should. The cell is fully resized and everything is visible. However, the problem resumes as soon as the text view goes to the next line, this time with all the previous text visible but none of the additional text.
If anyone has any ideas on what I might try next, it would be extremely helpful. Thanks!
Thanks to Steve asking for an example project that exhibits the behavior, I was able to figure out what is causing this issue.
To get the rounded corner effect, I had been using a layer mask on my cells. The mask uses the bounds of the cell to calculate its path, so when the cell updated itself to reflect the height of the text view, the mask was still in place and covered up the rest of the cell.
I didn't catch it because the rounding implementation was abstracted away in an extension. Whoops!

Load A UICollectionView's Data Before It's Added To The Screen

I have a situation where I need to have a UICollectionView have its data loaded (so I can fetch its content size) before it's been added to the view hierarchy.
I have tried calling reloadData and dispatching on the main_thread but to no avail
//The context manager needs to check with this to see if the scrollview can scroll (because otherwise it can't pan it up)
func hasEnoughContentForScrolling()->Bool
{
dispatch_sync(dispatch_get_main_queue(), {self.collectionView.reloadData()})
if(collectionView.contentSize.height > UIScreen.mainScreen().bounds.height)
{
return true
}
return false
}
Do you know how I can pre-load a UICollectionView before it's been added to the screen?
You can hide your collectionView in your viewDidLoad method and as collectionView is reloaded you can show your collection View with loaded data.
I tried to write the code in Swift. not sure it is the right code for a swift. but may be it can help you to understand-
override func viewDidLoad()
{super.viewDidLoad()self.collectionView.hidden = YES}
func hasEnoughContentForScrolling()->Bool
{
dispatch_sync(dispatch_get_main_queue(),{
self.collectionView.reloadData()self.collectionView.hidden = NO })
}

Resources