What are the differences between remembersLastFocusedIndexPath and indexPathForPreferredFocusedView(in:) methods? - ios

I have made a TvOS app which has 2 collection views they will be scrollable horizontally.
Now both of them have items which open corresponding cell's detail page.
That new page is pushed on navigation stack, when I come back I want my focus to remain on same cell of the collection view which opened the new page.
So, I found and tried two methods.
Method 1:
Setting collectionView.remembersLastFocusedIndexPath = true for both collection views
Method 2:
Using indexPathForPreferredFocusedView(in:) in delegate of both collection views.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// SOME WORK
focusedCellIndexPath = indexPath
}
override func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
return focusedCellIndexPath
}
Now when I used Method 1:
Say I have 10 cells in both collection view.
I was on cell 2 in first collection view, then I press down and scroll till the end on the second collection view and I press up then I go back to second cell of first collection view.
But when I used Mewthod 2:
When I press up then I don't go on second cell of first collection view but go on the cell just above currently focused cell of second collection view.
I want to know why they both behaved differently, looking at official documentation of method used in method2 in its discussion section it looks both things should behave similarly, but that is not the case here.

Related

Loading N views of UICollectionView and reload at the end

[EDIT after replies: solution at the end of the question]
I have a UICollectionView where I initially load 4 views. I would like that when the user scrolls until the last one (4th view) and try to scroll again, I update the data of my model, reload the UICollectionView and moves back to the 1st view so that the user can again scroll 3 times till the 4th view, etc.
The issue is that when I load 4 views initially, the user can't scroll after he reached the last view since there is nothing after. So I created an empty cell as my 5th view and now I load 5 views in my initial UICollectionView. But this 5th view is never fully displayed and I use "willDisplay" method to reload the data and force the move back to the first view. This is not elegant but I couldn't find another way to do it.
This method is working fine but here is the problem: it seems that when willDisplay is called, I indeed reload the data and moves back to the first view but only then the scroll that was supposed to take me to the 5th view is taken into account which means that after I moved back to the first view, the app shows me actually the second view (it did move back to the first view but then the scroll that I initiated to go from 4th to 5th view is applied, moving me to the 2nd view).
I hope this is clear...
So here is the code I am using:
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// if this is the last view
if((indexPath.row == self.dataModel.getNumberOfData()) && self.dataModel.getNumberOfData() > 0)
{
// I update the model here
self.dataModel.resetData()
// Then I reload the UICollectionView
self.collectionView.reloadData()
// Then I move back to the first view
self.collectionView.selectItem(at: IndexPath(item: 0, section: 0), animated: false, scrollPosition: .centeredHorizontally)
}
}
Of course if you have another solution, more elegant, to avoid that 5th view, I am happy to consider it. My data is not static so when I need to reload the new data, I query a database to retrieve the new information so I don't know about the new data when I run the app.
Thanks.
[EDIT with a solution using scrollViewDidScroll. I show for example how to detect the end (horizontally scrolling and delete the first view...
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
// if reached the end of the UICollectionView...
if (scrollView.contentOffset.x == scrollView.contentSize.width - scrollView.frame.size.width) {
print("Reached the end...")
self.dataModel.removeNFirst(n: 1) // remove first item from model
// delete the first view of the UICollectionView
self.collectionView.performBatchUpdates({
self.collectionView.deleteItems(at: [IndexPath(item: 0, section: 0)])
}) { (finished) in
self.collectionView.reloadData()
}
}
}

Customizing collection views and creating space between the collection view’s cells

I’m trying to create this relationship that the pictures below depict of Parent -> Children -> Children -> etc. . . I’m trying to use a vertical collection view for the Parent collection view and then nested horizontal collection views for the children. But I’m struggling with moving the collection views cells down when the “View Children Button” is clicked. I thought I could do something like this when the button is clicked:
#IBAction func showChildrenBtnClicked(_ sender: Any) {
collectionView.contentInset.bottom = 200
collectionView.reloadData()
}
but this doesn’t seem to do anything. Does anyone know how to add empty space inside the collection view so that I could then create a horizontal collection view to hold the children elements.
These are some mock images of the behavior I'm trying to achieve:
Step 1)
Step 2)
Step 3)
Step 4)
Step 5)
Thank you in advance.
First take all the view as collection view.
When ViewchildrenButton get tapped, delegate it to the parent collection view controller class, and there
if let flowLayout = metaMoreCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.invalidateLayout()
}
This will call the
collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
and give the updated size there.

UIButton behavior changes if collectionView moveItemAt is overridden

I have a collection view controller. One of collection view's cells contains a button with Touch Down and Touch Up Inside.
Strictly speaking, the button is in the view of view controller which is a subview of the collection view cell.
When the button is pressed the Touch Down is fired and when the button is released the Touch Up Inside is fired. Until the button is not released the button is grayed and the Touch Up Inside is not fired.
This is exactly the behaviour I need.
Now, I have implemented these two methods to support reordering collection view's cells:
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
If the second method is implemented (both if canMoveItemAt returns true or false), the button behavior changes:
as soon as the button is pressed, fires the Touch Down event and immediately after that, it also fires the Touch Up Inside event.
the button is grayed when pressed and immediately gets back to its normal state like it has been released
when the button is released, nothing happens
My suspect is that events propagate from button to the collection view controller, but I have no idea how to prevent it.
Can anyone help me?
Tks
You are right, but if you stop it, you stop reordering as well. Basically you need to choose what do you need: button to be pressed or cell to be dragged. Perhaps you can achive expected behaviour utilising Collection cell capabilities, instead of button. You can easily change button states, if you need button to represent that cell is being dragged.

didSelectItemAtIndexPath not called with multiple collection views on same screen

I have 2 collection views on the same screen, and I have the data source and delegate implemented for both in the same view controller. However, the delegate methods such as didSelectItemAtIndexPath is only called for one.
Other info:
Both collection views have a cell with an image.
AllowSelection and UserInteractionEnabled is set to true in both collection views
User interaction enabled on the images
Delegate and data source are set on the storyboard from both collection views to the view controller
Collection views are displayed properly
Both collection views have the same setup, yet only one delegate works. Would you have any clues what could be setting this up?
The collection views are inside a view that has a scroll view. Could this be somehow related?
EDIT 2:
Project with the same problem: https://github.com/iagomr/ProblemWithAutoLayout
EDIT 1:
Somehow this has to do with autolayout constraints, because if I pin the bottom collection view to the bottom of the screen instead of the bottom of other collection view, it starts working.
This is all due to the fact that I need to build a tall screen, and added everything into a view inside a scroll view 1000 points tall.
Code:
//MARK: - CollectionView Delegate
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("Called")
}
//MARK: - CollectionView DataSource
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == thisCV {
return 1
} else if collectionView == thisOtherCV{
return 1
}
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == "thisCV" {
if let thisCell = collectionView.dequeueReusableCellWithReuseIdentifier("thisCell", forIndexPath: indexPath) as? thisCollectionViewCell {
thisCell.image = image
return thisCell
}
} else if collectionView == "thisOtherCV"{
if let thisOtherCell = collectionView.dequeueReusableCellWithReuseIdentifier("thisOtherCell", forIndexPath: indexPath) as? OtherCollectionViewCell {
thisOtherCell.image = image
return thisOtherCell
}
}
return UICollectionViewCell()
}
I can confirm that didSelectItem is not getting called. If constant for top-bottom constraint between two collection views is changed from 501 to 0 it is working.
This problem is most likely related to the fact that you have two scroll views (collection views) inside of another scroll view. Overall, I would say, that you should modify your UI. I would suggest two ways of fixing it
Using single collection view
Use just one collection view with different sections for different content. Also, do NOT embed it in the scroll view - collection view already has a scroll view so you should be able to scroll easily. You can also dequeue different class of cells for different sections so you should be able to do everything which you want to do now.
If you want a starting point, here is a good tutorial which should help you with that.
Using scroll view
If you want to setup your UI in Interface Builder remove both collection views and simply add all of your UI inside of scroll view. Place UIButton in places where you want clicking to produce action.
You can even assign the same action to each button and then differentiate which one was triggered by assigning custom tags to each of them.

Handling button interaction of a dequeueReusableCell in iOS Swift

I am making an iOS app that relies on a table view. In each cell of the table view, there are 4 buttons aligned on the bottom. I have a cell class that is pretty standard and a feedController to handle the table and setting all the items of the cell.
Everything works fine on it but I can not figure out how to handle the button clicks within the cell. I can hard code it into my cell class, but then every 3 cells has the same interaction. Is there a way to pass the button click function from the cell class into the controller? I have tried checking the state from the controller and that has not worked.
Can you add a gesture recognizer as you're doing your cellForItemAtIndexPath? So I had something similar with a collection view, and what I did was as it within:
func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell!
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as MyCollectionView
...
I would add a gesture recognizer to each cell
i.e.
cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action:Selector("tapAction:")))
And then something like:
func tapAction(recognizer: UITapGestureRecognizer) {
...
}
so recognizer ends up being the specific item tapped, and I could take action accordingly (in my case, I had my datasource of items and I would find the item in an array by casting recognizer to a cell, finding the appropriate subview, and update values on it)
I would add code block properties to your cell class which the table can assign to deal with each button. In your cell, code each button handler to call the appropriate block, or pass an index for the button used in a single block.
See my answer here which has an example, but for a switch.
How can I get index path of cell on switch change event in section based table view
If after a few cells you get the same interaction, it's possibly because you're dequeueing a reusable cell, and you're getting the same cell.
Make sure to set your .setTarget() call for your buttons in your tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) data source every time the cell is dequeued. It would help if you shared how you're handling dequeuing to see if this is your issue.

Resources