tvOS CollectionView Selected Items Render Slow - ios

I'm having a collectionview setup, with local data and multiselect enabled. When I run this on an actual device I'm having some rendering issues. When I scroll superfast it takes a while before the active cells show up. They remain inactive for a little while when I stop and then suddenly pop up.
On didSelect I'm doing the following;
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! FRMenuCollectionViewCell
let selectedCells:[IndexPath] = collectionView.indexPathsForSelectedItems!
cell.isSelected = true
switch indexPath.section {
case 0:
let item = fixedData[indexPath.row]
itemSelection.append(item)
break
case 1:
handleContinentSelection(with: indexPath)
break
default:
print("An unexpected case occured")
}
}
func handleContinentSelection(with indexPath: IndexPath) {
let continent = continentData[indexPath.row]
for country in countryData {
if (country.continent?.continentName)! == continent {
itemSelection.append(country.countryName!)
if let index = countryData.lastIndex(of: country) {
collectionView?.selectItem(at: IndexPath(row: index, section: 2), animated: false, scrollPosition: [])
}
}
}
}
As illustrated above, all collectionview selections have a different way of handling the selection. If I select something in section 1, I also select cells in section 2.
The selected and deselected state are being handled in the subclass like this;
class FRMenuCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override var isSelected: Bool {
didSet {
if super.isSelected == true {
self.label.attributedText = NSMutableAttributedString(string: self.label.text!, attributes: FRSharedStyles.filledTextAttributes)
} else {
self.label.attributedText = NSMutableAttributedString(string: self.label.text!, attributes: FRSharedStyles.opaqueStrokeTextAttributes)
}
}
}
}
So the selected state is filled text, the deselected state is outlined. Everything works fine, it just renders slowly on my apple TV when scrolling fast (with the side scroll it's most noticable). Any ideas on how to optimise this would be appreciated.

Related

Update a single view with uicollection view Swift

I have a view with a grid movies. There's three properties: title Label, image view for poster movie and a image to show if a movie is or not favored.
When click on a cell, go on to a detail view with show others infos about the movie and there's a button action to favor a movie. I wish update the button icon in grid collection view. So I create a delegate to listen when this event occurs and then reload the collection view.
Screenshots:
grid movies screen
detail movie screen
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! MoviesCollectionViewCell
cell.titleLabel.text = popularMovies[indexPath.row].title
getImageMovies(imageURLString: popularMovies[indexPath.row].poster, imageView: cell.movieImage)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailMovieViewController = DetailMovieViewController()
detailMovieViewController.titleMovieLabel.text = popularMovies[indexPath.row].title
detailMovieViewController.releaseDateMovieLabel.text = popularMovies[indexPath.row].date
detailMovieViewController.overviewMovieLabel.text = popularMovies[indexPath.row].overview
getImageMovies(imageURLString: popularMovies[indexPath.row].poster, imageView: detailMovieViewController.movieImage)
getGenresMovies(genresMoviesID: popularMovies[indexPath.row].genre, genreMovieLabel: detailMovieViewController.genreMovieLabel)
collectionView.deselectItem(at: indexPath, animated: true)
detailMovieViewController.delegate = self
selectedIndexPath = indexPath
self.navigationController?.pushViewController(detailMovieViewController, animated: true)
}
protocol favoriteMovieDelegate: class {
func updateFavoriteImage ()
}
#objc func markFavoriteButtom (buttom: UIButton){
if buttom.isSelected == false {
buttom.isSelected = true
}else {
buttom.isSelected = false
}
delegate?.updateFavoriteImage()
}
func updateFavoriteImage() {
if let indexPath = selectedIndexPath {
let cell = collectionView.cellForItem(at: indexPath) as! MoviesCollectionViewCell
cell.favoriteIconImage.image = UIImage(named: "favorite_full_icon")
collectionView.reloadData()
}
}
struct Films: Codable {
let id: Int
let title: String
let poster: String
let genre: [Int]
let date: String
let overview: String
enum CodingKeys: String, CodingKey {
case id
case title
case poster = "poster_path"
case genre = "genre_ids"
case date = "release_date"
case overview
}
}
Instead of updating directly your cell.favoriteIconImage.image you will have to update your object directly and then reload CollectionView. you need to set fav image on cellForItem Also you need to post that to server once user make it fav ... and fetch isFav from server if you want to persist data. And if your server did not support isFav then you need to store it locally against film Id ... in user default

button, label is getting hide when i scroll table view up and down

I have a tableview, and in each cell I have one button called drop down. So when user presses any option in my drop down - the hidden elements like one more drop down, one name label, one save button will be visible. So again when user presses my save button again those elements will be hidden. Now the issues is when I select my button in two or three cells and if I scroll up and down automatically which and all cell showing the elements that and all getting hide. I need to show which and all cell is clicked and showed the elements.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartDetailsCell", for: indexPath) as! CartDetailsCell
cell.selectionStyle = UITableViewCellSelectionStyle.none
let notClicked = !selectedIndexPaths.contains(indexPath)
print(notClicked)
cell.noOfQtyOuterView.isHidden = notClicked
cell.saveDataButnOtlet.isHidden = notClicked
cell.noOfQtyButnOutlet.isHidden = notClicked
}
#IBAction func dropDownButnClick(_ sender: Any) {
guard let button = sender as? UIButton else {
return
}
let indexPath = IndexPath(item: button.tag, section: 0)
let cell = self.tableView.cellForRow(at: indexPath) as! CartDetailsCell
dropDown.anchorView = button
dropDown.dataSource = ["Edit", "Cancel"]
dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
switch index {
case 0:
cell.noOfQtyOuterView.isHidden = false
cell.saveDataButnOtlet.isHidden = false
cell.noOfComboOuterViewButn.isHidden = false
case 2:
}
}
Once the button is hidden it will never be un-hidden until you explicitly make it unhidden.
"Now the issues is when i select my button in two or three cells and if i scroll up and down automatically which and all cell showing the elements that and al getting hide"
let cell = tableView.dequeueReusableCell(withIdentifier: "CartDetailsCell", for: indexPath) as! CartDetailsCell
As you are using the cell with the hidden button is reused, it will make the button remain hidden for remaining cells
I suggest to use following pattern, will save you time and you'll have a more reusable and pretty code:
protocol CartDetailsCellDelegate: class {
func didTouchDropDownButton(in cell: CartDetailsCell)
....
}
final class CartDetailsCell: UITableViewCell {
....
weak var delegate: CartDetailsCellDelegate?
#IBAction func didTouchDropDownButton(_ sender: UIButton) {
delegate?.didTouchDropDownButton(in: self)
}
...
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
...
}
extension ViewController: CartDetailsCellDelegate {
func didTouchDropDownButton(in cell: CartDetailsCell) {
// Do your stuff here, you have the cell, don't have to play with tags
}
}

Adding sections to UITableView in 'Edit' Mode

I'm working on a basic list workout app right now that keeps track of workouts and then the exercises for each workout. I want to extend the current 'editing' mode of a TableViewController to allow for more advanced editing options. Here is what I have so far:
As you can see, I am inserting a section at the top of the table view so that the title of the workout can be edited. The problem I am facing is twofold:
There is no animation when the edit button is tapped anymore.
When you try to swipe right on one of the exercises (Squat or Bench press) the entire section containing exercises disappears.
I start by triggering one of two different functions on the setEditing function, to either switch to read mode or edit mode based on whether the boolean editing returns true or false.
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: true)
tableView.setEditing(editing, animated: true)
if editing {
switchToEditMode()
} else {
switchToReadMode()
}
}
Then I either insert the "addTitle" section (the text field seen in the second image) to an array called tableSectionsKey which I use to determine how to display the table (seen further below), and then reload the table data.
func switchToEditMode(){
tableSectionsKey.insert("addTitle", at:0)
self.tableView.reloadData()
}
func switchToReadMode(){
tableSectionsKey.remove(at: 0)
self.tableView.reloadData()
}
Here is my tableView data method. Basically the gist of it is that I have the array called tableSectionsKey I mentioned above, and I add strings that relate to sections based on what mode I'm in and what information should be displayed. Initially it just has "addExercise", which related to the "Add exercise to routine" cell
class WorkoutRoutineTableViewController: UITableViewController, UITextFieldDelegate, UINavigationControllerDelegate {
var tableSectionsKey = ["addExercise"]
}
Then in viewDidLoad I add the "exercise" section (for list of exercises) if the current workout routine has any, and I add the addTitle section if it's in new mode, which is used to determine if the view controller is being accessed from an add new workout button or a from a list of preexisting workouts (so to determine if the page is being used to create a workout or update an existing one)
override func viewDidLoad() {
super.viewDidLoad()
if workoutRoutine.exercises.count > 0 {
tableSectionsKey.insert("exercise", at:0)
}
if mode == "new" {
tableSectionsKey.insert("addTitle", at: 0)
}
}
Then in the cellForRowAt function I determine how to style the cell based on how the section of the table relates with an index in the tableSectionsKey array
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let section = indexPath.section
let sectionKey = tableSectionsKey[section]
let cellIdentifier = sectionKey + "TableViewCell"
switch sectionKey {
case "addTitle":
guard let addTitleCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? AddTitleTableViewCell else {
fatalError("Was expecting cell of type AddTitleTableViewCell.")
}
setUpAddTitleTableViewCell(for: addTitleCell)
return addTitleCell
case "exercise":
guard let exerciseCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ExerciseTableViewCell else {
fatalError("Was expecting cell of type ExerciseTableViewCell.")
}
let exercise = workoutRoutine.exercises[indexPath.row]
setUpExerciseTableViewCell(for: exerciseCell, with: exercise)
return exerciseCell
case "addExercise":
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
return cell
default:
fatalError("Couldn't find section: \(section), in WorkoutRoutineTableView" )
}
}
private func setUpExerciseTableViewCell(for cell: ExerciseTableViewCell, with exercise: Exercise) {
let titleText = exercise.name
let detailsText = "\(exercise.sets)x\(exercise.reps) - \(exercise.weight)lbs"
cell.titleLabel.text = titleText
cell.detailsLabel.text = detailsText
}
private func setUpAddTitleTableViewCell(for cell: AddTitleTableViewCell) {
cell.titleTextField.delegate = self
if (workoutRoutine.title != nil) {
cell.titleTextField.text = workoutRoutine.title
}
// Set the WorkoutRoutineTableViewController property 'titleTextField' to the 'titleTextField' found in the addTitleTableViewCell
self.titleTextField = cell.titleTextField
}
This isn't all of my code but I believe it is all of the code that could be relevant to this problem.
Your animation issue is due to the use of reloadData. You need to replace the uses of reloadData with calls to insert or delete just the one section.
func switchToEditMode(){
tableSectionsKey.insert("addTitle", at:0)
let section = IndexSet(integer: 0)
self.tableView.insertSections(section, with: .left) // use whatever animation you want
}
func switchToReadMode(){
tableSectionsKey.remove(at: 0)
let section = IndexSet(integer: 0)
self.tableView.deleteSections(section, with: .left) // use whatever animation you want
}

How can I change the properties of a cell that is not yet on the screen?

I've an app that consists of a timetable (UITableView) and a daybar (UICollectionView). The user can change their selected day by one of two methods:
Select a day from the DayBar
Click the "JumpToTomorrow" Button at the bottom of the table view.
The problem with 2. right now is that when I click it in a scenario where the next day has not yet been scrolled on to the screen, the cell for that day is not selected (see below).
DayCollectionViewCell.isSelected (not being called for some reason)
override var isSelected: Bool {
didSet {
self.backgroundColor = self.getBackgroundColor(selected: isSelected)
self.layer.borderColor = self.getBorderColor(selected: isSelected)
self.number.attributedText = NSAttributedString(string: number.text!, attributes: self.getTextAttributes(selected: isSelected))
}
}
DayBarController Functions
override func viewDidLoad() {
super.viewDidLoad()
// To preserve selection between presentations
self.clearsSelectionOnViewWillAppear = false
setEdgeInsets()
BroadcastManager.listen(to: PrepNotifications.JumpToTomorrowClicked, listenerId: "UpdateSelectedDay", react: updateSelectedDay)
}
/// change the selected day and notify the app about the change
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
PrepGlobals.Calendar.SELECTED_DAY = indexPath.row
BroadcastManager.broadcast(PrepNotifications.SelectedDayChanged)
//scroll to center on selected day in case of clicked on today
if (PrepGlobals.Calendar.TODAY_INDEX == indexPath.row){
self.collectionView?.scrollToItem(
at: indexPath,
at: .centeredHorizontally,
animated: true
)
}
}
private func updateSelectedDay(_ userInfo: [AnyHashable: Any]? = nil) {
PrepGlobals.Calendar.SELECTED_DAY += 1
let selectedDayIndex = IndexPath(row: PrepGlobals.Calendar.SELECTED_DAY, section: 0)
self.collectionView!.selectItem(at: selectedDayIndex, animated: true, scrollPosition: .right)
BroadcastManager.broadcast(PrepNotifications.SelectedDayChanged)
}
I edited my answer. Actually you have to set up isSelected manually.
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
cell.isSelected = (indexPath.row == PrepGlobals.Calendar.SELECTED_DAY)
}
You should modify your collectionView(_:cellForItemAt:) to check the cell index should be selected or not and update the style there.
Disabling Pre-fetching fixed this.

Weird behavior when scrolling and selecting in UICollectionView

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.

Resources