Reusing cells with hidden / scaled components - swift

I reuse collection view cell as a custom radio button. As a radio button selection I use two views:
border view
mark view
Mark view is hidden and scaled. It is basically simple selection.
In RadioButtonCollectionViewCell :
var checkMark: UIView = {
let view = UIView()
view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
view.backgroundColor = .darkGreyFts
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override var isSelected: Bool {
get {
return super.isSelected
} set {
super.isSelected = newValue
isSelected ? checkMark.scaleAnimate(1) : checkMark.scaleAnimate(-1)
}
}
override func prepareForReuse() {
super.prepareForReuse()
self.infoLabel.text = nil
self.checkMark.isHidden = false
}
UIView extension :
func scaleAnimate(_ state: Int) {
switch state {
case 1:
self.isHidden = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.transform = CGAffineTransform(scaleX: 1, y: 1)
})
case -1:
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
}, completion: { _ in
self.isHidden = true
})
default:
break
}
}
// MARK: - UICollectionView delegate.
extension DriverFormsViewController : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? RadioButtonCollectionViewCell {
cell.isSelected = true
collectionCellSelectedAtIndexPath = indexPath
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? RadioButtonCollectionViewCell {
cell.isSelected = false
}
}
}
// MARK: - UICollectionView data source.
extension DriverFormsViewController : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return optionsDictionary.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! RadioButtonCollectionViewCell
let key = optionsKeys[indexPath.row]
cell.infoLabel.text = optionsDictionary[key]
cell.checkMark.isHidden = true
if let collectionCellIsSelectedAtIndexPath = collectionCellSelectedAtIndexPath {
if collectionCellIsSelectedAtIndexPath == indexPath {
cell.checkMark.scaleAnimate(1)
}
}
return cell
}
}
How I would be able to reuse view which is hidden/not scaled/not ? Whenever I scroll down and back to the collection selected cell is not selected - mean, check mark is hidden and whenever I click on this cell it goes without animations. Any ideas what might be wrong ?
Thanks in advance!

You have to make sure to deselect the current cell before selecting a new one
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let currentSelectedIndexPath = collectionCellSelectedAtIndexPath,
let currentCell = cellForItem(at: currentSelectedIndexPath) as? RadioButtonCollectionViewCell {
currentCell.isSelected = false
}
if let cell = collectionView.cellForItem(at: indexPath) as? RadioButtonCollectionViewCell {
cell.isSelected = true
collectionCellSelectedAtIndexPath = indexPath
}
}
And instead of using prepareForReuse I would add an else clause in cellForItemAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! RadioButtonCollectionViewCell
let key = optionsKeys[indexPath.row]
cell.infoLabel.text = optionsDictionary[key]
cell.checkMark.isHidden = true
if let collectionCellIsSelectedAtIndexPath = collectionCellSelectedAtIndexPath,
collectionCellIsSelectedAtIndexPath == indexPath {
cell.checkMark.scaleAnimate(1)
} else {
cell.checkMark.scaleAnimate(-1)
}
return cell
}

Related

Collection view with dynamic cell sizes glitches on drag and drop

I have a CollectionView where all of my cells are aligned to the center of the view, utilising a custom flow layout. Here is the code for the collectionViewController:
let columnLayout = FlowLayout(
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
var dataSource: [String] = ["USA", "Brazil", "China","Really long long long", "Unite Kingdom", "Japan", "Mexico", "India", "Really long long", "Short", "USA", "Brazil", "China","Really long long long", "Unite Kingdom", "Japan", "Mexico", "India", "Really long long"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = columnLayout
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.register(TestCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.dragInteractionEnabled = true
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.delegate = self
collectionView.dataSource = self
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// dataArary is the managing array for your UICollectionView.
let item: String = dataSource[indexPath.row]
return CGSize(width: item.widthOfString(usingFont: .systemFont(ofSize: 16)) + 25, height: 40)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? TestCell
cell?.textLabel.text = dataSource[indexPath.row]
return cell!
}
fileprivate func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
if let item = coordinator.items.first,
let sourceIndexPath = item.sourceIndexPath {
collectionView.performBatchUpdates ({
self.dataSource.remove(at: sourceIndexPath.item)
self.dataSource.insert(item.dragItem.localObject as! String, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
}, completion: nil)
collectionView.reloadData()
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
}
Code for the FlowLayout Class:
class FlowLayout: UICollectionViewFlowLayout {
required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
super.init()
estimatedItemSize = UICollectionViewFlowLayout.automaticSize
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
sectionInsetReference = .fromSafeArea
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
guard scrollDirection == .vertical else { return layoutAttributes }
// Filter attributes to compute only cell attributes
let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
// Group cell attributes by row (cells with same vertical center) and loop on those groups
for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
// Get the total width of the cells on the same row
let cellsTotalWidth = attributes.reduce(CGFloat(0)) { (partialWidth, attribute) -> CGFloat in
partialWidth + attribute.size.width
}
// Calculate the initial left inset
let totalInset = collectionView!.safeAreaLayoutGuide.layoutFrame.width - cellsTotalWidth - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(attributes.count - 1)
var leftInset = (totalInset / 2 * 10).rounded(.down) / 10 + sectionInset.left
// Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
for attribute in attributes {
attribute.frame.origin.x = leftInset
leftInset = attribute.frame.maxX + minimumInteritemSpacing
}
}
return layoutAttributes
}
}
I then added a UICollectionViewDragDelegate and a UICollectionViewDropDelegate extensions in order to enable the user to reorder the cells. The two extensions:
extension CollectionViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let item = self.dataSource[indexPath.row]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
}
extension CollectionViewController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
if collectionView.hasActiveDrag {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UICollectionViewDropProposal(operation: .forbidden)
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
var destinationindexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationindexPath = indexPath
} else {
let row = collectionView.numberOfItems(inSection: 0)
destinationindexPath = IndexPath(item: row - 1, section: 0)
}
if coordinator.proposal.operation == .move {
self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationindexPath, collectionView: collectionView)
}
}
}
For some strange reason the cells glitch out in the app when reordering them:
https://imgur.com/dmEQLqh
Thanks for the help

Invisible cells of UICollectionView are not deselected - Swift

I have in my swift app two collection views. One which is a functions categories and another one which is the functions. The first one works as a filter to the second one. If I select "Cat1" then only functions with tag "Cat1" are displayed. This works great.
The functions categories collectionview is horizontal and I need to scroll to see all the cells. My issue/problem is already mentioned in another topics but I can not find the right anwser or technique.
Issue: If I select a category, the cell's background changes, fine. If now I scroll completely to the end of the collectionview and select the last cell, this one change as selected the the first one (previously selected) is not deselected.. I know that is a problem with reused cell but no idea how to manage that. Below my code :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Working with functions categories collection view
if collectionView == self.functionsCategoriesCollectionView {
let cell = collectionView.cellForItem(at: indexPath) as! DevicePageFunctionsCategoriesCVCell
cell.isHidden = false
cell.cellView.isHidden = false
cell.isSelected = true
cell.cellView.clipsToBounds = true
cell.cellView.layer.cornerRadius = 25
cell.cellView.addGradiant(colors: [UIColor(red: 127.0/255.0, green: 127.0/255.0, blue: 127.0/255.0, alpha: 1.0).cgColor, UIColor(red: 47.0/255.0, green: 47.0/255.0, blue: 47.0/255.0, alpha: 1.0).cgColor], angle: 45)
if cellSelectionIndexPath == indexPath {
// it was already selected
cellSelectionIndexPath = nil
collectionView.deselectItem(at: indexPath, animated: true)
cell.cellView.addGradiant(colors: [UIColor.clear.cgColor, UIColor.clear.cgColor], angle: 0)
self.filtered = GlobalVariables.currentProduct.functions.filter { _ in
return true
}
self.functionsCollectionView.reloadData()
} else {
// wasn't yet selected, so let's remember it
cellSelectionIndexPath = indexPath
// Filter with seletec category name
let cellCategoryName = ICDatabase.objects(FunctionCategory.self)[indexPath.row]
self.filtered = GlobalVariables.currentProduct.functions.filter { function in
return function.functionCategory.contains(cellCategoryName)
}
self.functionsCollectionView.reloadData()
}
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == self.functionsCategoriesCollectionView {
if let cellToDeselect = collectionView.cellForItem(at: self.cellSelectionIndexPath) as? DevicePageFunctionsCategoriesCVCell {
cellToDeselect.isSelected = false
collectionView.deselectItem(at: self.cellSelectionIndexPath, animated: true)
cellToDeselect.cellView.addGradiant(colors: [UIColor.clear.cgColor, UIColor.clear.cgColor], angle: 0)
self.cellSelectionIndexPath = nil
// Get all functions
self.filtered = GlobalVariables.currentProduct.functions.filter { _ in
return true
}
self.functionsCollectionView.reloadData()
}
}
}
Thanks for your help!
Try this -
var selectedIndexPath: IndexPath?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell.layer.backgroundColor = UIColor.black
self.selectedIndexPath = indexPath
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell.layer.backgroundColor = UIColor.white
self.selectedIndexPath = nil
}
Collection cells reuse memory. So you have to manually manage data and UI. If your requirement is to select only one category cell in collection view at any time, keep one variable in collection view and update it in didSelect method.
var selectedIndexPath: IndexPath?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = UICollectionCell()
....
cell.backgroundColor = UIColor.black
if indexPath == selectedIndexPath {
cell.backgroundColor = UIColor.red
}
}

Deselecting Collection View Cell Not Changing Variable Value

I am trying to change my "selected" variable so that when a character cell in the collectionview is selected, the view controller can be dismissed when the user clicks the button (sendTapped). However, it is not changing the variable value back to false when the user deselects a cell, so the view controller is dismissed when the user deselects a cell and then clicks the button.
class CharactersViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let characters = [...]
let characterImages = [...]
var selectedIndex: Int?
var selected = false
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func sendTapped(_ sender: Any) {
***if selected {
self.dismiss(animated: true, completion: nil)
let initialViewController = UIStoryboard.initialViewController(for: .main)
self.view.window?.rootViewController = initialViewController
self.view.window?.makeKeyAndVisible()
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.characters.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CharacterViewCell
cell.characterImage.image = UIImage(named: characterImages[indexPath.row])
if selectedIndex == indexPath.row {
cell.contentView.layer.borderColor = UIColor.yellow.cgColor
cell.contentView.layer.borderWidth = 2.0
}else{
cell.contentView.layer.borderColor = UIColor.clear.cgColor
cell.contentView.layer.borderWidth = 1.0
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let character = characters[indexPath.row]
***selected = true
selectedIndex = selectedIndex == indexPath.row ? nil : indexPath.row
ProfileService.updateChild(child: "char", childVal: character)
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
***selected = false
}
}

UICollectionview - blink when move item

I want to reorder my cells in my UICollectionView. But when I drop my item, the "cellForItemAt" method is called and this will cause the cell to flash (See image below).
What should I do to avoid this behavior ?
Thank you in advance for your help.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let cellIdentifier = "cell"
private let cells = [""]
private var longPressGesture: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongGesture(gesture:)))
collectionView.addGestureRecognizer(longPressGesture)
}
//Selectors
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
You need to call endInteractiveMovement in perfomBatchUpdates.
But whenever endInteractiveMovement triggered, cellForRow called. So cell will be refreshed and new cell will added(check with random color extension). To secure that, you need to save selectedCell in variable. And return that cell when endInteractiveMovement called.
Declare currentCell in ViewController
var isEnded: Bool = true
var currentCell: UICollectionViewCell? = nil
Store selected cell in variable when gesture began & call endInteractiveMovement in performBatchUpdates.
So, your handleLongGesture func look like below:
//Selectors
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
break
}
isEnded = false
//store selected cell in currentCell variable
currentCell = collectionView.cellForItem(at: selectedIndexPath)
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case .ended:
isEnded = true
collectionView.performBatchUpdates({
self.collectionView.endInteractiveMovement()
}) { (result) in
self.currentCell = nil
}
default:
isEnded = true
collectionView.cancelInteractiveMovement()
}
}
Also need to change cellForRow
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if currentCell != nil && isEnded {
return currentCell!
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
cell.backgroundColor = .random
return cell
}
}
TIP
Use random color extension for better testing
extension UIColor {
public class var random: UIColor {
return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
}
EDIT
If you have multiple sections.
Lets take array of array
var data: [[String]] = [["1","2"],
["1","2","3","4","5","6","7"],
["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"]]
Then you need to maintain data when reordering
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
print("\(sourceIndexPath) -> \(destinationIndexPath)")
let movedItem = data[sourceIndexPath.section][sourceIndexPath.item]
data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
data[destinationIndexPath.section].insert(movedItem, at: destinationIndexPath.item)
}
You can try to call
collectionView.reloadItems(at: [sourceIndexPath, destinationIndexPath])
right after all your updates (drag and drop animation) are done.
For example call it after performBatchUpdates. It will remove blinking.

Swift - Return array data in tableView with custom filter

I have a UITableView within a UIViewController that loads the ranking of users. Having only one ranking list was a little bit too limited, so I have added a custom bar to load different rankings (weekly, monthly and yearly). The reason why I chose to do it this way is because it gave me a lot of control over my layout constraints - segmented control does not.
The current problem is that I don't exactly know how to return the right array based on the selected tab in my menu bar. As of now I use a fourth empty array to copy the data of one of the other three when the tab is selected, but how do I send this data back to my initial view so that I can return the count in my tableView's numberOfRowsInSection?
ViewController & TableViewController
class Rank: NSObject{
var name: String
var points: Int
init(name: String, points: Int) {
self.name = name
self.points = points
}
}
var rankingArrayWeek = [Rank]()
var rankingArrayMonth = [Rank]()
var rankingArrayTotal = [Rank]()
var filteredRanking = [Rank]()
class RankingController: UIViewController, UITableViewDelegate {
weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
tableView?.delegate = self
rankingArrayWeek = [
Rank(name: "Name1", points: 200)
]
rankingArrayMonth = [
Rank(name: "Name1", points: 300),
Rank(name: "Name2", points: 200),
]
rankingArrayTotal = [
Rank(name: "Name1", points: 500),
Rank(name: "Name2", points: 400),
Rank(name: "Name3", points: 300),
]
let rankingTableViewController = RankingTableViewController()
self.addChildViewController(rankingTableViewController)
rankingTableViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(rankingTableViewController.view)
rankingTableViewController.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
rankingTableViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 10).isActive = true
rankingTableViewController.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
rankingTableViewController.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
lazy var menuBar: MenuBar = {
let menuBar = MenuBar()
menuBar.rankingController = self
return menuBar
}()
private func setupMenuBar() {
navigationController?.hidesBarsOnSwipe = true
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(50)]", views: menuBar)
}
}
// MARK: TableViewController
class RankingTableViewController: UITableViewController {
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
tableView?.register(RankCell.self, forCellReuseIdentifier: cellId)
tableView?.tableFooterView = UIView(frame: CGRect.zero)
tableView?.rowHeight = 60
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredRanking.count
}
}
My custom menubar
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.isScrollEnabled = false
collectionView.backgroundColor = .white
collectionView.dataSource = self
collectionView.delegate = self
return collectionView
}()
let cellId = "cellId"
let names = ["Week", "Month", "Total"]
var rankingController: RankingController?
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
let selectedIndexPath = NSIndexPath(item: 2, section: 0)
collectionView.selectItem(at: selectedIndexPath as IndexPath, animated: false, scrollPosition: .centeredHorizontally)
setupHorizontalBar()
}
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = Constants.MAIN_THEME_COLOR
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
horizontalBarView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/3).isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 4).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let x = CGFloat(indexPath.item) * frame.width / 3
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: { self.layoutIfNeeded()
}, completion: nil)
if indexPath.item == 0 {
filteredRanking = rankingArrayWeek
print(filteredRanking.count)
} else if indexPath.item == 1 {
filteredRanking = rankingArrayMonth
print(filteredRanking.count)
} else {
filteredRanking = rankingArrayTotal
print(filteredRanking.count)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.buttonView.text = "\(names[indexPath.item])"
cell.buttonView.textColor = Constants.MAIN_THEME_COLOR
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 3, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Updated code:
To preselect the first row, I also added this to my cellForItemAt indexPath:
if (cell.isSelected == false) {
didSelect?(kinds[0])
}
Suggest that you add a callback on your menu bar that says what kind of rank has been selected. You can also use this "Kind" to drive the display of the menu bar.
enum RankKind: String {
case week = "Week"
case month = "Month"
case total = "Total"
}
class MenuBar {
let kinds = [RankKind.week, .month, .total]
var didSelect: ((RankKind)->())?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return kinds.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
cell.buttonView.text = kinds[indexPath.item].rawValue)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
didSelect?(kinds[indexPath.item])
}
}
Then the RankingController can set itself up to know when the menu has changed kind.
class RankingController {
func viewDidLoad() {
menuBar.didSelect = { kind in
rankingTableViewController.rankingArray = self.rankingArrayFor(kind: kind)
}
}
func rankingArrayFor(kind: RankKind) -> [Rank] {
switch kind {
case .week: return rankingArrayWeek
case .month: return rankingArrayMonth
case .total:return rankingArrayTotal
}
}
}
Lastly, the RankingTableViewController exposes its model (an array), and reloads its tableview when that model is reset.
class RankingTableViewController: UITableViewController {
var rankingArray: [Rank] = [] {
didSet {
self.tableView.reloadData()
}
}
}
The above code is additional to the original question's code existing for brevity, i.e it is not stand alone.