I am running into an issue where I am navigating from one UICollectionView to another upon selection of the cell.
The issue is the flashing of previous data. It only happens when I click "back" on the navigation bar. I have included the code where navigation occurs.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.selectedIndexPath = indexPath
let vc = self.storyboard?.instantiateViewController(withIdentifier: kSecondVCId) as? SecondCollectionViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
The values are stagnant arrays of values in both views.
Video of issue: https://vimeo.com/263987657
I am using the following library from GitHub, there is an open issue for this but I'm hoping someone will have thoughts on a possible fix.
The collection view reuses the same cell, so its very simple you have to reset the content before reusing them, else the old cache data will show.
reset the data in UICollecitonViewCell (or) UITableViewCell class using this override function(prepare for reuse)
override func prepareForReuse() {
//just reset the text from here
self.Button.setTitle(title: "", for: .normal)
//reset any thing in here which u don't wanna see again
super.prepareForReuse()
}
Hope this helps, some one. Happy coding !
Related
I have a collection view scrollable in 2-dimensions. Each row is a section with some cells.
Now in each row only one item is special (will have enum state = special, otherwise normal).
I want a behavior such that when we move up or down and we were on a special cell we should jump to special cell of next row.
View that behavior here
I did it like this, I will be storing previous focused cell's indexpath in a variable and when I move to next cell then in collectionView(:canFocusItemAt:) I will check if previous was special and we are changing the section then only next cell is focusable if it's special.
func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
if let previousFocusIndex = focusIndex,
previousFocusIndex.section != indexPath.section,
let previousCell = collectionView.cellForItem(at: previousFocusIndex) as? EpisodeCell,
let nextCell = collectionView.cellForItem(at: indexPath) as? EpisodeCell,
previousCell.timeState == .special {
print("🟢#Searching_Episode: \(nextCell.description)")
return nextCell.timeState == .special
}
return true
}
But now I came to know about behavior of focus engine that its search area is based on current cell's width.
And is a cell in next row does not lies in that search area it will not focus there.
Problem I am facing
I tried googling but didn't find anything better and can't think what else I can try.
If you have an idea it will be really helpful. Thanks.
There are 2 parts to solve this problem.
Identify the user is seeing the special content shown in collection view[Special Item]
This can be achieved by checking the Visible Items and cross check with data source that if it is special
var indexPathsForVisibleItems: [IndexPath] { get }
Scrolling to Special Item in a particular section
Based on the out come of step 1 we should decide calling scrollToItem
func scrollToItem(at: IndexPath, at: UICollectionView.ScrollPosition, animated: Bool)
You may put this logic when you identify that user is scrolling the collection view by confirming to UIScrollViewDelegate, which is inherited by UICollectionViewDelegate.
protocol UIScrollViewDelegate //Protocol to use
func scrollViewDidScroll(UIScrollView)
//Tells the delegate when the user scrolls the content view within the scroll view.
We need to have optimised logic here such that scrollToItem is only called when there is a new row is showed.
I have a collection view which loads a list of products and there is no fancy functionality in it. It just loads data from API. The problem is that when i try to tap on one of the items in the collection view
collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
is not getting called. But when i long press on a cell it gets called.
I am pretty sure that there are no gestures applied on the cell.
Can anyone give me some guidelines to tackle the problem.
The code base that i have is as follows
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellTapped = (self.storeSearchListingCollectionViewOutlet.cellForItem(at: indexPath)) as! StoreSearchProdListingCollectionViewCell
UStoreShareClass.storeSharedInstance.longTappedProductID = String(cellTapped.tag)
if((UStoreShareClass.storeSharedInstance.longTappedProductID) != nil)
{
self.performSegue(withIdentifier: "searchToProductDetailSegue", sender: self)
}
else
{
SetDefaultWrappers().showAlert(info:SERVER_DOWN_ERROR_ALERT, viewController: self)
}
}
View Hierarchy is as follows
Thanks in Advance...!!!
This is intentional. On the Storyboard go to the Collection View and uncheck "Delay Touch Down" in the Attribute inspector.
May have two reasons:
1) May be first reason is:
Unfortunately your code is not getting main thread to work on UI. So you have to put your UI related part in MAIN thread like this.
DispatchQueue.main.async {
// PUT YOUR CODE HERE
if((UStoreShareClass.storeSharedInstance.longTappedProductID) != nil)
{
self.performSegue(withIdentifier: "searchToProductDetailSegue", sender: self)
}
else
{
SetDefaultWrappers().showAlert(info:SERVER_DOWN_ERROR_ALERT, viewController: self)
}
}
2) In second reason:
Have you made collectionView bouncing turned off?
In Storyboard, make checked "Bounce on scroll" checkbox of collectionView, if unchecked.
For future readers of this old question - probably me in the future ;)
I had a UITapGestureRecognizer which was swallowing my short taps (in the UICollectionView's grandparent) and hence only the old taps were making it through! I recon you have the same problem. You just need to add tapRecognizer.cancelsTouchesInView = false!
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapDidTouch(sender:)))
tapRecognizer.cancelsTouchesInView = false
scrollView.addGestureRecognizer(tapRecognizer)
First of all , put breakpoint in if condition and check if it gets inside the didselect block. Then,
Try adding your "performsegue" code in main thread like below
if((UStoreShareClass.storeSharedInstance.longTappedProductID) != nil)
{
DispatchQueue.main.async
{
self.performSegue(withIdentifier: "searchToProductDetailSegue", sender: self)
}
}
else
{
SetDefaultWrappers().showAlert(info:SERVER_DOWN_ERROR_ALERT, viewController: self)
}
Its getting called in viewDidLoad, after fetching the data used.
After some print debugging it looks like it calls all the appropriate delegeate methods, if no data is changed. If there has been some data changed, cellForItemAt does not get called.
Reloading the whole section works fine. But gives me an unwanted animation. Tried disabling UIView animation before, and enabling after reloading section, but still gives me a little animation.
collectionView.reloadSections(IndexSet(integer: 0))
Here is my current situation, when using reloadData()
The UICollectionViewController is a part of a TabBarController.
I'm using custom UICollectionViewCells. The data is loaded from CoreData.
First time opening the tab, its works fine.
After updating the favorites item in another tab, and returning to this collectionView, its not updated. But if i select another tab, and go back to this one, its updated.
var favorites = [Item]()
override func viewWillAppear(_ animated: Bool) {
collectionView!.register(UINib.init(nibName: reuseIdentifier, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
if let flowLayout = collectionView!.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 1,height: 1)
}
loadFavorites()
}
func loadFavorites() {
do {
print("Load Favorites")
let fetch: NSFetchRequest = Item.fetchRequest()
let predicate = NSPredicate(format: "favorite == %#", NSNumber(value: true))
fetch.predicate = predicate
favorites = try managedObjectContext.fetch(fetch)
if favorites.count > 0 {
print("Favorites count: \(favorites.count)")
notification?.removeFromSuperview()
} else {
showEmptyFavoritesNotification()
}
print("Reload CollectionView")
collectionView!.reloadData(
} catch {
print("Fetching Sections from Core Data failed")
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("Get number of section, \(favorites.count)")
if favorites.count > 0 {
return favorites.count
} else {
return 0
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("Get cell")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SubMenuCell
let menuItem = favorites[indexPath.row]
cell.title = menuItem.title
cell.subtitle = menuItem.subtitle
return cell
}
Console Print/Log
Starting the app, going to the CollectionView tab where there are no favorites:
Load Favorites
Get number of section, 0
Get number of section, 0
Reload CollectionView
Get number of section, 0
Switching tab, and adding and setting an Item object's favorite to true, then heading back to the CollectionView tab:
Load Favorites
Favorites count: 1
Reload CollectionView
Get number of section, 1
The datamodel has 1 item, reloading CollectonView, cellForRowAtIndex not called?
Selecting another tab, random which, then heading back to the CollectionView tab, without changing any data.
Load Favorites
Favorites count: 1
Reload CollectionView
Get number of section, 1
Get cell
Now my Item shows up in the list. You can see from the Get Cell that cellForItemAt is called aswell. Nothing has changed between these last two loggings, just clicked back/fourth on the tabs one time.
Forgot to mention, but this code IS working fine in the simulator.
On a read device its just not giving me any error, just not showing the cell (or calling the cellForItemAt)
After some more debugging i got an error when the items got reduced (instead of increasing like i've tried above).
UICollectionView received layout attributes for a cell with an index path that does not exist
This led med to iOS 10 bug: UICollectionView received layout attributes for a cell with an index path that does not exist
collectionView!.reloadData()
collectionView!.collectionViewLayout.invalidateLayout()
collectionView!.layoutSubviews()
This solved my problem.
I am using autoresizing cells, but i guess this does not explain why cellForItemAt did not get called.
in my case, my collectionview was in a stackview, i didnt make any constraints, when i added the necessary constraints it worked.
Just check collectionview's constraints.
Ok, maybe I can help someone =)
I do this:
Load data from API
Then after load data call DispatchQueue.main.async like this
DispatchQueue.main.async {
self.pointNews = pointNews
}
In self.pointNews didSet I try to do collectionView.reloadData(), but it work only, if I call DispatchQueue.main.async in didSet
DispatchQueue.main.async {
self.collectionView.reloadData()
}
var paths: [IndexPath] = []
if !data.isEmpty {
for i in 0...salesArray.count - 1 {
paths.append(IndexPath(item: i, section: 0))
}
collectionView.reloadData()
collectionView.reloadItems(at: paths)
}
This is the code helped me, for solving this kind of issue.
Post your code.
One possibility is that you are using `URLSession and trying to tell your collection view to update from the it's delegate method/completion closure, not realizing that that code is run on a background thread and you can't do UI calls from background threads.
I faced the same problem with self resizing cells and above solution works as well but collection view scroll position resets and goes to top. Below solution helps to retain your scroll position.
collectionView.reloadData()
let context = collectionView.collectionViewLayout.invalidationContext(forBoundsChange: collectionView.bounds)
context.contentOffsetAdjustment = CGPoint.zero
collectionView.collectionViewLayout.invalidateLayout(with: context)
collectionView.layoutSubviews()
i was facing also this kind of issue so finally i am able to solved using this line of code
[self.collectionView reloadData];
[self.collectionView reloadItemsAtIndexPaths:#[[NSIndexPath indexPathWithIndex:0]]];
I had the same issue. I was updating the height of the UICollectionView based on the content and reloadData() stops working when you set the height of the collection view to zero. After setting a minimum height. reloadData() was working as expected.
Steps taken:
Created new single view project
Deleted default view controller
Added new Table View Controller
Embedded TVC into a Navigation Controller
Added a child TVC
Added custom class files and associated to each TVC
Created a Show segue from the first TVC to the child
Implemented required methods, #of sections, # of rows, cellForRowAtIndexPath
All of the tutorials I have watched and read online only include the steps I show above, and the segues start working fine. However, in my case I can not get it to work until I add the following:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("detail", sender: tableView)
}
I'm totally fine with implementing the didSelectRowAtIndexPath method if it is required. I'm just wondering if I am missing something, because it seems to work automatically in everything I've seen online.
Thanks in advance!
If you click on the segue (storyboard) and on the side menu you have to assign the text "detail" from
performSegueWithIdentifier("detail", sender: tableView)
on the Identifier field.
Like the image below
Edit:
Try the code above, rembember to change NAME_OF_THE_DETAIL_VIEW_CONTROLLER to your Detail ViewController.
In this code we set i set "self" before the performSegueWithIdentifier. And when the button is clicked you set the segue.destinationViewController to your new ViewController.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detail", sender: indexPath);
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "detail") {
var detailView = segue.destinationViewController as! NAME_OF_THE_DETAIL_VIEW_CONTROLLER
}
}
Thank you all for your contributions. I figured out that the problem was the following was missing from the cellForRowAtIndexPath tableView function.
let cell = self.tableView.dequeueReusableCellWithIdentifier("custom", forIndexPath: indexPath) as! CustomCell
I had this line commented out, mostly because I have no idea what it does. :)
I'm relatively new to Swift and am stumbling on this problem that is likely very simple to fix, I just can't figure it out and keep hitting a brick wall.
I'm making an app where a user taps an image in a collection view controller and it performs an unwind segue back to the main view controller and updates a uiimageview with the image that the user selected. I got the unwind segue to work but the problem is that the uiimageview is always one image behind (i.e. I select image A, unwind segue back and no image displays, so I select image B unwind segue back and then the original image I selected the first time, image A, is in the uiimageview).
Here is the code for the destination view controller (main view controller where I want the uiimageview to update based on the image selected in the collection view)...
#IBAction func unwindToVC(segue:UIStoryboardSegue) {
if(segue.sourceViewController .isKindOfClass(FrancoCollectionCollectionViewController))
{
let francoCollectionView:FrancoCollectionCollectionViewController = segue.sourceViewController as! FrancoCollectionCollectionViewController
let selectedImageFromLibrary = selectedFranco
dragImage!.image = UIImage(named: selectedImageFromLibrary)
}
On the collection view controller where the user selects the image and it unwinds back, I just ctrl + dragged the cell to "exit" on the storyboard and selected the unwindToVC segue.
As far as my collection view here is how I have that set up, since I have a suspiscion it's related to the didSelectItemAtIndexPath part of it but I'm not sure...
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return francoPhotos.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! FrancoCellCollectionViewCell
// sets each cell of collection view to be a uiimage view of francoPhotos
let image = UIImage(named: francoPhotos[indexPath.row])
cell.francoImageCell.image = image
return cell
}
// highlights cell when touched
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedFranco = francoPhotos[indexPath.row]
let cell = collectionView.cellForItemAtIndexPath(indexPath)
cell!.layer.borderWidth = 3.0
cell!.layer.borderColor = UIColor.blueColor().CGColor
}
override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
cell!.layer.borderWidth = 0.0
cell!.layer.borderColor = UIColor.clearColor().CGColor
}
Sorry if I posted way too much code but this is my first post and like I said I'm new to development, so I figured it would be better to include a little extra rather than not have something that is needed.
Any advice is greatly appreciated!
Thanks so much!!! :)
This is happening because collectionView:didSelectItemAtIndexPath: is called after the segue is triggered. So, it is always one behind.
One way to fix this is to call the unwind segue programmatically with performSegueWithIdentifier instead of wiring it from the cell to the exit. To do this, wire your exit segue from the viewController icon (left most icon) at the top of your viewController to the exit icon (right most icon).
Find this segue in the Document Outline view and give it an identifier such as "myExitSegue" in the Attributes Inspector on the right. Then in didSelectItemAtIndexPath, the last thing to do is to call performSegueWithIdentifier("myExitSegue", sender: self).
Here's what I would do:
Remove the code in the source VC unwind action for an empty unwind segue like so (the logic for displaying the new image will be put in the collection VC):
#IBAction func unwindToVC(segue: UIStoryboardSegue) {
}
Give your unwind segue a string identifier so that you may perform the segue programmatically not automatically. Here, I just called mine "Unwind".
Then, inside of FrancoCollectionCollectionViewController put a property to hold the selected UIImage:
var selectedFranco: UIImage!
Within didSelectItemAtIndexPath set the property to the selected image and then manually perform the segue:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// ...
self.selectedFranco = francoPhotos[indexPath.row]
self.performSegueWithIdentifier("Unwind", sender: self)
}
Then I would add the prepareForSegue method in your FrancoCollectionCollectionViewController to set the source view controller's image to the selected image:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Unwind" {
let sourceVC = segue.destinationViewController as! YourSourceViewController
sourceVC.dragImage.image = selectedImageView.image
}
}