I have a list of user avatars inside of a UICollectionViewCell. When the user taps on one, I'd like to add the selected item to a collection as well as highlight it to indicate it's been tapped.
Unfortunately the UI doesn't seem to update. Any ideas?
Initially I load the images and set them to be rounded. I can even set the border color, if I want, but for now I set it to clear. This all works upon loading the cells:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath) as! UICollectionViewCell
// Configure the cell
let member: UserProfile = groupMembers[indexPath.item]
let imgAvatar = cell.viewWithTag(2) as! UIImageView
imgAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
imgAvatar.clipsToBounds = true
imgAvatar.contentMode = UIViewContentMode.scaleAspectFill
imgAvatar.layer.borderWidth = 2.0
imgAvatar.layer.borderColor = UIColor.clear.cgColor
imgAvatar.layer.cornerRadius = 30.0
let downloadURL = NSURL(string: member.avatarUrl)!
imgAvatar.af_setImage(withURL: downloadURL as URL)
return cell
}
And now here is the code that executes when you tap on any given UIImageView in the collection, but it does not seem to update the image:
///Fired when tapped on an image of a person
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath) as! UICollectionViewCell
let tappedUser: UserProfile = groupMembers[indexPath.item]
let imgAvatar = cell.viewWithTag(2) as! UIImageView
..add item to collection, etc..
//Update imageview to indicate it's been tapped.
DispatchQueue.main.async {
contactAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
imgAvatar.clipsToBounds = true
imgAvatar.contentMode = UIViewContentMode.scaleAspectFill
imgAvatar.layer.borderWidth = 2.0
imgAvatar.layer.cornerRadius = 30.0
imgAvatar.layer.borderColor = UIcolor.blue.cgColor
}
}
}
Running this code, it hits the breakpoint to indicate I've tapped on the item, but it does not update the UI. I'm convinced there is a thread / ui issue where the collection view isn't "redrawing" the changes I've made to the image. Maybe I can't change around the appearances of a view inside of a collection?
Thanks in advance for any insight.
Your didSelect is not correct. Get the cell from the collection view.
cellForItem
///Fired when tapped on an image of a person
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at:indexPath) as! UICollectionViewCell
let tappedUser: UserProfile = groupMembers[indexPath.item]
let imgAvatar = cell.viewWithTag(2) as! UIImageView
..add item to collection, etc..
//Update imageview to indicate it's been tapped.
DispatchQueue.main.async {
contactAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
contactAvatar.clipsToBounds = true
contactAvatar.contentMode = UIViewContentMode.scaleAspectFill
contactAvatar.layer.borderWidth = 2.0
contactAvatar.layer.cornerRadius = 30.0
contactAvatar.layer.borderColor = UIcolor.blue.cgColor
}
}
}
Using mobile so let me know if it does not work.
The code which you have written to to get instance of cell is not correct please use below line of code and rest all seems correct hope by changing this line will work.
let cell = collectionView.cellForItem(at:indexPath) as! UICollectionViewCell
Related
I added buttons in my collection view cells by the code below, where 'myButton' refers to the buttons I try to make access to.
When I click some button outside the collectionView, I want one of my buttons to have its background image changed to have the background of the button clicked, which I tried with 'sendToBox' function below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! ItemCollectionViewCell
cell.myButton.setTitle(self.items[indexPath.item], for: .normal)
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 0.5
return cell
}
...
#IBAction func keyClick(_ sender: UIButton) {
...
sendToBox(object: sender)
...
}
...
func sendToBox(object sentObject: UIButton) {
let imageToSend = sentObject.backgroundImage(for: .normal)
let imageIdentity = sentObject.restorationIdentifier
let cell = self.collectionView(ItemCollectionSet, cellForItemAt: IndexPath(item: 0, section: 0))
cell.myButton ## blah blah not working!
}
So I want to make a direct access to one of the cells I created, and then change the button inside it to have a different appearance. I'm stuck in this matter for a whole day, please help me out.
I have a TableView that is embedded into a CollectionView, and I am trying to show relevant data in the TableView that corresponds to the correct CollectionViewCell or IndexPath Item. I tried assigning tag as such: cell.tableView.tag = indexPath.item but it seems to be problematic.
I tried print(tableView.tag) in my collectionViewCell and it printed
2 1 0 3 4 5
but I have 7 collectionViewCells in total so the last tag isn't printing for some reason.
My collectionView is embedded in another TableView already, below is the code in the MasterTableViewCell.swift:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if diningIndexPath.section == 0 {
let cell: FoodCourtCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "foodCourtCell", for: indexPath) as! FoodCourtCollectionViewCell
cell.tableView?.register(UINib(nibName: "RestaurantTableViewCell", bundle: nil), forCellReuseIdentifier: "restaurantCell")
cell.tableView.tag = indexPath.item
//...
return cell
}
}
In the customCollectionViewCell.swift, I have this code for my tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: RestaurantTableViewCell = tableView.dequeueReusableCell(withIdentifier: "restaurantCell", for: indexPath) as! RestaurantTableViewCell
print(tableView.tag)
let currentRestaurant = foodCourts[tableView.tag].childLocations[indexPath.row]
cell.textLabel.text = currentRestaurant.name
//...
return cell
}
Is there any way to fix this, or are there other ways to achieve what I want to do? Any help is appreciated, thanks!
Nesting these entities is always a pain especially when you need to access indexPaths of items later. If if get your problem correctly. One of the solutions I suggest is to store a map (dictionary) of your paths. For a fast access to them. Here's an example of how I managed this in a similar situation:
typealias CollectionIndexPath = IndexPath
typealias TableIndexPath = IndexPath
var indexMap: [CollectionIndexPath: TableIndexPath] = [:]
Now when you need to access some of the items or configure it.
func cellForItemAtIndexPath { ... } {
let cell = { ... }
let cellPath = indexPath
let tablePath = indexMap[cellPath]
let foodCourtForCell = foodCourts[cellPath.item]
let childLocationsForTableView = foodCourtForCell.childLocations
cell.configureWith(court: foodCourtForCell, locations: childLocations)
Now you can manage all the data related to this nested monster from the outside.
I have a UICollectionView and have two cell types. ShareCell and ShareCellMedia. I have it so that ShareCellMedia is only returned when the property .hasImage is true. However, when implementing the logic in cellforitem at index path, it returns all cells as either ShareCell or ShareCellMedia when in this case, because of the data I'm using one cell should be a media cell while the others are regular.
Below is the code in cell for item at index path
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath :
IndexPath) -> UICollectionViewCell {
let friend = fetchedResultsController.object(at: indexPath) as! Friend
if (friend.lastMessage?.hasImage)! == false {
let mediaCell = collectionView.dequeueReusableCell(withReuseIdentifier: mediaCellId, for: indexPath) as!
ShareCellMedia
mediaCell.cell = friend.lastMessage
return mediaCell
}
else if (friend.lastMessage?.hasImage)! == true {
let regularCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as!
ShareCell
regularCell.cell = friend.lastMessage
return regularCell
}
return UICollectionViewCell()
}
any suggestions?
Are you fetching the correct friend object? As you're using indexPath, not indexPath.row
My cellforitem at index path wasn't the cause of the problem.
Thanks in part #DonMags comment I was able to deduce that the function I used to the messages didn't have the variable .hasImage properly instantiated.
This question already has answers here:
How can I fix crash when tap to select row after scrolling the tableview?
(2 answers)
Closed 6 years ago.
I have used a collection view to display a collection of images. It is working, but when I make use of the function didDeselectItemAt it will crash if I click certain images.
Error that occurs:
fatal error: unexpectedly found nil while unwrapping an Optional value
Code setup:
numberOfItemsInSection:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellIdentifier = "ImageCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! ImageCollectionViewCell
let imageUrls = collection?.imageUrls
// Configure the cell
// Reset alpha of first item in collection
if indexPath.row == 0 {
cell.imageView.alpha = 1.0
if videoUrl != nil {
cell.backgroundColor = UIColor.red
cell.imageView.alpha = 0.5
}
}
cell.imageView.af_setImage(withURL: URL(string: (imageUrls?[indexPath.row])!)!)
return cell
}
didDeselectItemAt:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! ImageCollectionViewCell
UIView.animate(withDuration: 0.3, animations: {
cell.imageView.alpha = 0.6
})
}
didSelectItemAt:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You've tapped me. NICE!! \(indexPath.row)")
let cell = collectionView.cellForItem(at: indexPath) as! ImageCollectionViewCell
cell.imageView.alpha = 1
let url = self.collection?.imageUrls?[indexPath.row]
self.selectedImageUrl = url
Alamofire.request(url!).responseImage(completionHandler: {
(response) in
if response.result.value != nil {
self.selectedImage.image = response.result.value
}
})
}
Above code is working, but will throw an error if I click certain images. The first cell of every collection view has a alpha of 100% - 1.0 and all other ones has an alpha of 0.6. What I noticed is that - except the first cell of the collection view - every 7th cell has also an alpha of 100% and if the collection view contains a videoUrl it will have a red background and an alpha of 50%..
Is this because of the reusable cells like the UITableViewController, where you use dequeueReusableCell and should you use that function too inside the didDeselectItemAt or is something else not correctly implemented?
So only the first cell in a collection view should have an alpha of 100, all other 60%. If the object has a videoUrl, the first element will have an alpha of 50% with a red background.
If there are any questions left, please let me know. Thanks in advance and have a great evening!
UPDATE:
The following print line will output below code when I click the first image of the collection view and after that the second one. This doesn't crash the app and it will still work
print("OUTPUT \(String(describing: collectionView.cellForItem(at: indexPath)))")
Above code snippet I've placed in both didSelectItemAt and didDeselectItemAt:
didSelectItemAt output:
OUTPUT Optional(<CollectionViewApp.ImageCollectionViewCell: 0x7ff61679c1a0; baseClass = UICollectionViewCell; frame = (150 0; 150 100); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x6080000347c0>>)
didDeselectItemAt output:
OUTPUT Optional(>)
When I click the first or second image and then the last image in the collection view, the error will occur and will crash my app. The touch on the first image will give me the output:
OUTPUT Optional(<CollectionViewApp.ImageCollectionViewCell: 0x7fc3cb038540; baseClass = UICollectionViewCell; frame = (150 0; 150 100); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x610000225980>>)
The last item - that will let my app crash - will throw the following output:
OUTPUT Optional(<CollectionViewApp.ImageCollectionViewCell: 0x7fc3cb03afb0; baseClass = UICollectionViewCell; frame = (300 0; 150 100); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x6100002264c0>>).
So the crash can be triggered, when you click one of the first images in the collection view and after that one of the last images in the collection view, when the first images are out of canvas. I used a horizontal collection view.
So the crash is happening because when selecting an item, didDeselectItemAt can be called for an item that's not visible anymore.
The UICollectionView reuses UICollectionViewCells, this means that the collectionView.cellForItem(at: indexPath) method will return nil for cells that are not visible.
To fix this you can use the following code:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as! ImageCollectionViewCell else {
return //the cell is not visible
}
UIView.animate(withDuration: 0.3, animations: {
cell.imageView.alpha = 0.6
})
}
Some other suggestions I have for improving your code:
You have a lot of places where you're force unwrapping values.
Consider taking the following approach:
guard let url = self.collection?.imageUrls?[indexPath.row] else {
fatalError("url was nil")
}
self.selectedImageUrl = url
Alamofire.request(url).responseImage(completionHandler: {
(response) in
if response.result.value != nil {
self.selectedImage.image = response.result.value
}
})
By using the guard statement you force the app to crash with an appropriate error message, which will help you when debugging. It's a better practice compared to force unwrapping.
Also, when dequeing cells, you could go for something like this:
let cellIdentifier = "ImageCell"
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? ImageCollectionViewCell else {
fatalError("Wrong cell type")
}
This can happen when the cell type is different
I am having issues with displaying a checkmark on the a custom cell in a UICollectionView. For the first few taps everything works as expected, but when I begin scrolling or tapping repeatedly or click on the already selected cell, the behavior becomes odd as shown in the gif. Perhaps I am going about this in an incorrect way? The .addCheck() and .removeCheck() are methods inside the custom UICollectionViewCell class I made and all they do is add a checkmark image or remove one from the cell view. The odd behavior shown here
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ColorUICollectionViewCell
// Configure the cell
let color = colorList[(indexPath as NSIndexPath).row]
cell.delegate = self
cell.textLabel.text = color.name
cell.backgroundColor = color.color
if color.selected {
cell.addCheck()
}
else {
cell.removeCheck()
}
return cell
}
// user selects item
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for color in colorList {
color.selected = false
}
// set selected color to true for selection
let color = colorList[indexPath.row]
color.selected = true
settings.backgroundColor = color.color
//userDefaults.set(selectedIndex, forKey: "selectedIndex")
collectionView.reloadData()
}
Below is what the addCheck() and removeCheck() functions in my custom cell look like.
func addCheck() {
// create check image
let checkImage = UIImage(named: "checkmark")
checkImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.size.height / 4, height: bounds.size.height / 4))
checkImageView.image = checkImage!.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
checkImageView.tintColor = UIColor.white()
// add the views
addSubview(checkImageView)
}
func removeCheck() {
if checkImageView != nil {
checkImageView.removeFromSuperview()
}
}
first off, you can simplify your didSelect a bit:
override func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for (index, color) in colorList.enumerate() {
if index == indexPath.row {
color.selected = false
settings.backgroundColor = color.color
}
else {
color.selected = false
}
}
collectionView.reloadData()
}
Based on the language in your cellForItemAt method, I'm guessing you're adding a second check mark image when you tap on the same cell twice, and it's not being tracked properly so that cell just keeps getting rotated around overtime the collectionView's reloaded
Post your cell class, or at least the logic for addCheck and removeCheck and we might find the problem.
What I would recommend is permanently having an imageView with the check mark over the cell, when simple show/hide it based on the selection. This should speed up the collectionView as well.