How to scroll to a given indexPath.item? - swift

I have 2 collection views in parallel. One of them is a menu and the other is a big one containing vertical table views that are populated with API queried data.
When the user scrolls right, then Order History is highlighted and the next cell in the big collection view at the bottom takes over the rest of the screen and it shows its own tableview data. It's just like the implementation here.
Everything works EXCEPT when I tap on Order History in the state indicated in the attached image, the collection view at the bottom does not scroll to the 2nd cell in the big collection view. If I tap on the Current Orders cell then it does scroll to the 1st cell in the big collection view. I have no idea what's causing this problem after spending the last 48 hours on this.
This is the menu bar:
class MenuBar: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var menuCollection: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionview = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionview.translatesAutoresizingMaskIntoConstraints = false
collectionview.backgroundColor = .white
collectionview.delegate = self
collectionview.dataSource = self
collectionview.register(InvoiceCell.self, forCellWithReuseIdentifier: InvoiceCell.identifier)
return collectionview
}()
var leftIndicatorConstraint: NSLayoutConstraint?
private let menuOptions = ["Current Orders", "Order History"]
var containerViewController: ContainerViewController? // I use an instance of the parent VC to pass on the value
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(menuCollection)
menuCollection.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
menuCollection.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
menuCollection.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
menuCollection.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
menuCollection.selectItem(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .left)
setupIndicator()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupIndicator() {
let indicatorBar = UIView()
indicatorBar.backgroundColor = UIColor(hex: Constants.Colors.primary)
addSubview(indicatorBar)
indicatorBar.translatesAutoresizingMaskIntoConstraints = false
leftIndicatorConstraint = indicatorBar.leadingAnchor.constraint(equalTo: self.leadingAnchor)
leftIndicatorConstraint?.isActive = true
indicatorBar.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
indicatorBar.heightAnchor.constraint(equalToConstant: 5).isActive = true
indicatorBar.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5).isActive = true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
containerViewController?.scrollToMenu(menuIndex: indexPath.item) // Here I pass on the value of the menu item that's tapped
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuOptions.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: InvoiceCell.identifier, for: indexPath) as? InvoiceCell else { return UICollectionViewCell() }
cell.thisOption = menuOptions[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: menuCollection.frame.size.width / CGFloat(menuOptions.count), height: menuCollection.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
And this is the parent UIView Controller that contains the menu collection view as well as the big base collection view
class ContainerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var menuBar: MenuBar = {
let menuBar = MenuBar()
menuBar.containerViewController = self
menuBar.translatesAutoresizingMaskIntoConstraints = false
return menuBar
}()
lazy var baseCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(PastCell.self, forCellWithReuseIdentifier: PastCell.pastCellId)
collectionView.register(CurrentCell.self, forCellWithReuseIdentifier: CurrentCell.currentCellId)
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .white
return collectionView
}()
var orderViewModel = OrderViewModel(order: Order(addressOne: "", addressTwo: "", city: "", postalCode: "", mpName: "", planType: 0, restaurantAddress: "", restaurantId: "", restaurantName: "", timeOfCreation: nil, currentTotal: 0.00, orderMenu: [], status: 0, itemsHTML: ""))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupView()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
orderViewModel.removeListener()
}
private func setupView() {
view.addSubview(menuBar)
menuBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
menuBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
menuBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
menuBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
view.addSubview(baseCollectionView)
baseCollectionView.topAnchor.constraint(equalTo: menuBar.bottomAnchor, constant: 0).isActive = true
baseCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
baseCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
baseCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func scrollToMenu(menuIndex: Int) {
// Here I am using the passed on menu index to scroll to the appropriate cell in the big collection view.
let indexPath = IndexPath(item: menuIndex, section: 0)
baseCollectionView.scrollToItem(at: indexPath, at: .left, animated: true) // For some reason it ALWAYS SCROLL TO THE FIRST CELL }
func scrollViewDidScroll(_ scrollView: UIScrollView) {
menuBar.leftIndicatorConstraint?.constant = scrollView.contentOffset.x / 2
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = Int(targetContentOffset.pointee.x / view.frame.width)
menuBar.menuCollection.selectItem(at: IndexPath(item: index, section: 0), animated: true, scrollPosition: .left)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.row {
case 0:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CurrentCell.currentCellId, for: indexPath) as? CurrentCell {
if let tabBarHeight = self.tabBarController?.tabBar.frame.size.height {
cell.adjustSize(top: menuBar.frame.height * 1.2, bottom: tabBarHeight * 0.5)
return cell
}
}
case 1:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PastCell.pastCellId, for: indexPath) as? PastCell {
cell.backgroundColor = .systemOrange
return cell
}
default:
return UICollectionViewCell()
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width, height: view.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}

Turns out there's a big bug with scrollToItem that Apple hasn't fixed. In my case it's caused by isPagingEnabled. So the best solution I found was here where you disable paging before scrolling to item and then enabling it right again.

Related

Why are my collectionview cells not animating?

I have collection view cells and I'd like to animate all of them at the same time. Even though I'm using UIView.animate the animation does not happen and affine transform happens instantaneously.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var collectionview: UICollectionView!
var moveText: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
collectionview = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionview.translatesAutoresizingMaskIntoConstraints = false
collectionview.dataSource = self
collectionview.delegate = self
collectionview.backgroundColor = .systemBackground
collectionview.register(MyCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(collectionview)
NSLayoutConstraint.activate([
collectionview.topAnchor.constraint(equalTo: view.topAnchor),
collectionview.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionview.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionview.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.moveText = moveText
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionview.frame.width, height: 120)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
UIView.animate(withDuration: 1, delay: 0) {
print("Starting animatings")
self.moveText = !self.moveText
self.collectionview.reloadData()
}
}
}
}
class MyCell: UICollectionViewCell {
let label = UILabel()
var moveText: Bool = false {
didSet {
if moveText == true {
label.transform = CGAffineTransform(translationX: 50, y: 50)
} else {
label.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
label.text = "mytext"
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.topAnchor.constraint(equalTo: topAnchor)
])
backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is because you are animating the wrong thing.
// adding weak self so the timer will not retain the view controller
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
self?.moveText.toggle() // you can use toggle() on a `Bool`
self?.collectionview.reloadData()
}
Instead, you should try to animate setting the value of individual cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
UIView.animate(withDuration: 1, delay: 0) {
cell.moveText = self.moveText
}
return cell
}
Also, instead of using CGAffineTransform(translationX: 0, y: 0) you could use .identity
label.transform = .identity
Worth noting is the fact that reloading collection view every 2 seconds and animating directly in cellForItemAt is very inefficient. You should find another way to pass information to the cell's they should animate the content

how to prevent delay when trying to animate invisible cells in collectionview

I would like to animate all of my collectionview cells and this simplified example illustrates the problem. The problem is that if I scroll down while animating cells the ones that are not visible gets animated with a delay. I would like invisible cells to just be in the final state of animation instead of having animation delay.
In the video you see that when I scroll to the bottom, the final cells starts to animate with delay.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var collectionview: UICollectionView!
var moveText: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
collectionview = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionview.translatesAutoresizingMaskIntoConstraints = false
collectionview.dataSource = self
collectionview.delegate = self
collectionview.backgroundColor = .systemBackground
collectionview.register(MyCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(collectionview)
NSLayoutConstraint.activate([
collectionview.topAnchor.constraint(equalTo: view.topAnchor),
collectionview.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionview.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionview.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
UIView.animate(withDuration: 1, delay: 0, options: [
UIView.AnimationOptions.allowAnimatedContent,
UIView.AnimationOptions.allowUserInteraction,
]) {
cell.moveText = self.moveText
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionview.frame.width, height: 120)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
print("Starting animatings")
self.moveText.toggle()
self.collectionview.reloadData()
}
}
}
class MyCell: UICollectionViewCell {
let label = UILabel()
var moveText: Bool = false {
didSet {
if moveText == true {
label.transform = CGAffineTransform(translationX: 50, y: 50)
} else {
label.transform = .identity
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
label.text = "mytext"
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.topAnchor.constraint(equalTo: topAnchor)
])
backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
instead of animating inside the cellForItemAt add this helper function.
private func animateVisibleCells() {
collectionview.visibleCells.compactMap { $0 as? MyCell }.forEach { cell in
UIView.animate(withDuration: 1, delay: 0, options: [
UIView.AnimationOptions.allowAnimatedContent,
UIView.AnimationOptions.allowUserInteraction,
]) {
cell.moveText = self.moveText
}
}
}
Now call it instead of reloading the collection view
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
print("Starting animatings")
self.moveText.toggle()
self.animateVisibleCells()
}
and convert the cellForItemAt implementation to:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.moveText = moveText
return cell
}

Cannot assign value of type '(JournalListViewController) -> () -> JournalListViewController' to type 'JournalListViewController?'

I am trying to show another ViewController from a custom tab bar but, I cannot pass a view controller to the custom class.
I've tried var jlvc = HomeController() but that would crash the app.
class JournalListViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
print(self)
navigationController?.navigationBar.isTranslucent = false
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width-32, height: view.frame.height))
titleLabel.textColor = UIColor.white
titleLabel.text = "Journal List"
titleLabel.font = UIFont.systemFont(ofSize: 20)
navigationItem.titleView = titleLabel
collectionView?.backgroundColor = UIColor.white
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell class
collectionView?.register(JournalCell.self, forCellWithReuseIdentifier: "cellId")
// To unblock view from menubar
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 60, right: 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 60, right: 0)
// Do any additional setup after loading the view.
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
mb.jlvc = self
return mb
}()
private func setupMenuBar(){
view.addSubview(menuBar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat(format: "V:[v0(60)]|", views: menuBar)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (view.frame.width - 16 - 16) * 9 / 16 //-16 for top, - 16 for bot, 16/9 aspect ratio so * by 9/16
return CGSize(width:view.frame.width, height:height + 16 + 41) //height is height if image + additional space we added
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func showHomeController(){
let hc = HomeController()
navigationController?.pushViewController(hc, animated: true)
}
import Foundation
import UIKit
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var jlvc: JournalListViewController?
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor(red: 152/255, green: 216/255, blue: 142/255, alpha: 1)
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
let imageNames = ["home","camera","journal"]
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat(format: "H:|[v0]|", views: collectionView)
addConstraintsWithFormat(format: "V:|[v0]|", views: collectionView)
let selectIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectIndexPath, animated: false, scrollPosition: [])
self.collectionView.isScrollEnabled = false
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
// Returning Cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.black
cell.jlvc = self.jlvc
return cell
}
// Size of Cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 3, height: frame.height)
}
// Spaceing in between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
// Selecting cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! MenuCell
if cell.imageView.image == UIImage(named: "home")?.withRenderingMode(.alwaysTemplate) {
jlvc?.showHomeController()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuCell: BaseCell {
var jlvc: JournalListViewController?
let imageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "home")?.withRenderingMode(.alwaysTemplate)
iv.tintColor = UIColor.black
return iv
}()
override var isHighlighted: Bool {
didSet{
imageView.tintColor = isHighlighted ? UIColor.white : UIColor.black
}
}
override var isSelected: Bool {
didSet{
imageView.tintColor = isSelected ? UIColor.white : UIColor.black
}
}
override func setupViews() {
super.setupViews()
addSubview(imageView)
addConstraintsWithFormat(format: "H:[v0(28)]", views: imageView)
addConstraintsWithFormat(format: "V:[v0(28)]", views: imageView)
addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
}
}
Cannot assign value of type '(JournalListViewController) -> () -> JournalListViewController' to type 'JournalListViewController?'
let menuBar: MenuBar = {
let mb = MenuBar()
mb.jlvc = self
return mb
}()
This doesn't do what you're thinking. self in this context is the class, not the instance. You either meant to make this a lazy var (as you did with collectionView), or to assign it explicitly.

Overriding Margin between UITableView and Custom Cell

I have a UITableView created in code as such:
lazy var tableView:UITableView = {
let tv = UITableView()
tv.delegate = self
tv.dataSource = self
tv.remembersLastFocusedIndexPath = false
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}
I am constraining it to the parent view:
private func setup() {
tableView.register(LiveTVCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20).isActive = true
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
}
The cusom cell here has nothing but a red background. As rendered, the cell is showing 90 points from the left side of the screen where the tableview starts. I have tried to use layoutMargins as well as contentInset to no effect. If I do something like: print(self.tableView.layoutMargins) it shows that the left margin is indeed 90.
The only way I have been able to overcome this behavior is to add the following: tv.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: -180, bottom: 0, trailing: -90) I loath this solution as it relies on magic numbers.
Net, how can I remove this 90 points of left margin without the above hack fix?
More code has been asked for so I am including the tableview delegate and datasource functions below:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.liveArray.first?.shows.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! LiveTVCell
if !self.fetchedRows.contains(indexPath.row) {
self.fetchedRows.append(indexPath.row)
self.spinner.start()
MediaFeedController.retrieveShows(showIDs: liveArray.first!.shows,forSection:indexPath.row) { [unowned self](row) in
self.spinner.stop()
guard row != nil else {return}
UIView.performWithoutAnimation {
self.tableView.reloadRows(at: [IndexPath(row: row!, section: 0)], with: .none)
}
}
}
cell.showID = liveArray.first!.shows[indexPath.row]
//print(self.tableView.layoutMargins)
return cell
}
func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
Here is the tableviewcell. Keep in mind that the issue occurs if I remove all of this from the tableviewcell and just set a background color:
import UIKit
class LiveTVCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var showID:Int! {
didSet {
setup()
}
}
private var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func layoutSubviews() {
super.layoutSubviews()
}
func setup() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor(named: "collectionView")
collectionView.delegate = self
collectionView.dataSource = self
collectionView.remembersLastFocusedIndexPath = true
collectionView.register(ChannelCVCell.self, forCellWithReuseIdentifier: "channelCell")
collectionView.register(ProgramCVCell.self, forCellWithReuseIdentifier: "programCell")
self.addSubview(collectionView)
collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
// MARK: - Collectionview Delegate & Datasource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let channelCell = collectionView.dequeueReusableCell(withReuseIdentifier: "channelCell", for: indexPath) as! ChannelCVCell
channelCell.showID = showID
return channelCell
} else {
let programCell = collectionView.dequeueReusableCell(withReuseIdentifier: "programCell", for: indexPath) as! ProgramCVCell
programCell.showID = showID
return programCell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.section == 0 {
return CGSize(width: 177, height: 70)
} else {
return CGSize(width: collectionView.frame.size.width, height: 70)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
} else {
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
}
}
// MARK: - CollectionView Focus Delegates
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
//guard let index = context.nextFocusedIndexPath?.row else {return}
}
override func prepareForReuse() {
self.collectionView.removeFromSuperview()
}
}
Update:
You can completely remove the custom cell and have nothing in cellForRowAt and return a generic UITableViewCell and the table's layout margin on the left is 90. In fact the second you add the table to the subview and anchor it, it's 90.

to dismiss a viewcontroller using collectionView cell

i am having a difficulty on dismissing a viewController when i click a collectionviewcell. so my collectionview is placed inside a uiview that is then displayed in a viewcontroller. whenever the user click on collectionViewCell, i want the view to trigger bye() function that is placed inside the viewcontroller. i add print("bye") just to see if it work, and it does print the word but it does not execute dismiss(animated: true, completion: nil) to dismiss the viewcontroller along with the uiview and collectionview. why it does not dismiss the controller? is there another way that i can do the same thing? here is the code :
the view controller
class sideViewController: UIViewController {
let dismissBtn:UIButton = {
let content = UIButton()
content.backgroundColor = .green
content.addTarget(self, action: #selector(bye), for: .touchUpInside)
return content
}()
let sideTableViews: sideCollectionView = {
let content = sideCollectionView()
return content
}()
override func viewDidLoad() {
super.viewDidLoad()
dismissBtn.translatesAutoresizingMaskIntoConstraints = false
sideTableViews.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(dismissBtn)
view.addSubview(sideTableViews)
dismissBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
dismissBtn.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
dismissBtn.widthAnchor.constraint(equalToConstant:40).isActive = true
dismissBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
sideTableViews.topAnchor.constraint(equalTo: dismissBtn.bottomAnchor, constant: 30).isActive = true
sideTableViews.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
sideTableViews.widthAnchor.constraint(equalTo: view.widthAnchor, constant: 0).isActive = true
sideTableViews.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
#objc func membershipController(){
let next = self.storyboard?.instantiateViewController(withIdentifier: "membershipViewController") as! membershipViewController
self.present(next, animated: true, completion: nil)
}
#objc func bye(){
print("bye")
dismiss(animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
this is the collectionView code and the uiview
class sideCollectionView:UIView, UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
let arrayLbl = ["connection","achievement","template","setting"]
let arrayImg = ["connection","achievement","template","setting"]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayLbl.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! sideCollectionViewCell
cell.titleImg.image = UIImage(named: "\(arrayImg[indexPath.row])")
cell.titleLbl.text = arrayLbl[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (self.frame.width / 2) - 40, height: (self.frame.width / 2) - 40)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(25, 25, 10, 25)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 0{
connectController()
print("connection")
let side = sideViewController()
side.bye()
}
if indexPath.row == 1{
let side = sideViewController()
side.bye()
print("achievement")
}
if indexPath.row == 2{
let side = sideViewController()
side.bye()
print("template")
}
if indexPath.row == 3{
let side = sideViewController()
side.bye()
print("setting")
}
}
lazy var collectionViews: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.clear
cv.dataSource = self
cv.delegate = self
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews(){
collectionViews.register(sideCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionViews.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = UIColor.clear
addSubview(collectionViews)
collectionViews.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
collectionViews.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
collectionViews.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0).isActive = true
collectionViews.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0).isActive = true
collectionViews.widthAnchor.constraint(equalTo: widthAnchor, constant: 0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func connectController(){
let side = sideViewController()
side.bye()
}
#objc func settingController(){
let side = sideViewController()
side.bye()
}
#objc func achievementController(){
let side = sideViewController()
side.bye()
}
#objc func templateController(){
let side = sideViewController()
side.bye()
}
}
Because this
let side = sideViewController()
is another instance other than the displayed one , so add this var to the view
class sideCollectionView:UIView {
var currentVc:sideViewController?
}
//
when you create the variable assign it
lazy var sideTableViews: sideCollectionView = {
let content = sideCollectionView()
content.currentVc = self
return content
}()
//
Inside
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 0{
connectController()
print("connection")
currentVc?.bye()
}
}