Custom collection view cell disappears on tap - ios

I'm working on an iPad app that should display 4 CollectionViews next to each other. The height of the collection views should be 3/4 of the screen, so the layout will look something like this:
___________________
| top content |
|-------------------|
| CV | CV | CV | CV |
|____|____|____|____|
I tried to narrow everything down and created a new project with just one of these collection views, but I keep running into the same problem: when I tap on one of the cells, all of them disappear. To reproduce:
create a new project with template "Single View Application" using Swift as language
setup the storyboard:
drag a new Collection View Controller in the storyboard
set the storyboard ID to "CollectionViewController"
for the cell: set identifier to MyCollectionViewCell, drag a label in the cell, set constraints
create files with the following source code:
CollectionViewCell.swift: (create the outlet by ctrl-dragging the label to the source code)
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
}
CollectionViewController.swift: (pay attention to the comment in viewDidLoad implementation)
import UIKit
private let reuseIdentifier = "MyCollectionViewCell"
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Register cell classes
// this has to be removed to work with a custom cell class
// self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
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: reuseIdentifier, for: indexPath) as! CollectionViewCell
cell.label.text = "\(indexPath.section)-\(indexPath.row)"
return cell
}
}
change the implementation for ViewController.swift:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let frame = view.frame
let vc = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "CollectionViewController")
vc.view.frame = CGRect(x: 0.0, y: frame.size.height * 0.25, width: frame.size.width, height: frame.size.height * 0.75)
self.view.addSubview(vc.view)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here's a capture of the iOS simulator:
https://youtu.be/VVBsTnYLGM4
I hope I didn't forget anything to reproduce the problem. Just in case, I uploaded the project to my dropbox.
https://dl.dropboxusercontent.com/u/607872/CollectionViewBugZip.zip
Can someone tell me what I'm doing wrong?

Turns out the mistake was in adding the collection view as a subview, which isn't considered good practice. Appearently, when only the subview is added, it's "disconnected" from its view controller.
This did the trick:
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let frame = view.frame
let vc = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "CollectionViewController")
self.addChildViewController(vc) // <----------- !!!!
vc.didMove(toParentViewController: self) // <-- !!!!
vc.view.frame = CGRect(x: 0.0, y: frame.size.height * 0.25, width: frame.size.width, height: frame.size.height * 0.75)
self.view.addSubview(vc.view)
}

Check that your ViewController not deiniting. Write deinit { print("Deinited") } and place a breakpoint there. In my case CollectionView disappeared because it was been deiniting with viewController that holt it.

Swift 5 / iOS 16
Adding the following two lines, right below where I programmatically instantiate the UIViewController that hosts the UICollectionView worked for me).
I don't know why it works, but I was completely out of ideas and found this on reddit, and it solved it after a couple of hours of me sweating and trying everything I could think of:
self.addChild(vcThatHostsCollectionView)
vcThatHostsCollectionView(toParent: self)
This is the code in my 'consumer' viewcontroller that instantiates the collection view hosting viewcontroller, that includes the two lines shown above.
func configurePagerTabs() {
let rect = CGRectMake(0, 0, UIScreen.main.bounds.width, 35.0)
let pagerTabsViewController = PagerTabsViewController(frame: rect, tabTitles: tabTitles)
self.addChild(pagerTabsViewController)
pagerTabsViewController.didMove(toParent: self)
pagerTabsViewController.delegate = self
pagerTabsView = pagerTabsViewController.view
view.addSubview(pagerTabsView)
}
This is part of the viewcontroller that creates the collection view, to show how it initializes things.
class PagerTabsViewController: UIViewController {
enum Section { case main }
var dataSource : UICollectionViewDiffableDataSource<Section, String>! = nil
var collectionView : UICollectionView! = nil
var tabTitles : [String]! = nil
var delegate : PagerTabsDelegate? = nil
var reuseId = "TabCellReuse"
var frame = CGRect.zero
required init(coder: NSCoder) { fatalError("Not implemented to use with storyboard") }
#objc init(frame: CGRect, tabTitles: [String]) {
super.init(nibName: nil, bundle: nil)
self.tabTitles = tabTitles
self.frame = frame
}
override func loadView() {
collectionView = UICollectionView(frame: frame, collectionViewLayout: createLayout())
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = .blue
registerCells()
view = collectionView
}
.
.
.
}

Related

UIViewController multiple inheritance

I have a class called BaseViewController which contains a function where I can add a header to my VC and anchor it
class BaseViewController: UIViewController {
let headerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.green
return view
}()
func addHeader() {
view.addSubview(headerView)
// then anchor it to top
}
}
I have another class called ScrollViewController which adds a scrollViewController to my VC and anchors it
class ScrollViewController: UIViewController {
let scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = UIColor.green
return view
}()
func addScrollView() {
view.addSubview(scrollView)
// then anchor it to top
}
}
Finally my main class HomeViewController wants to have both a header and a scrollView so I inherit from both like this:
class HomeViewController: ScrollViewController, BaseViewController {
override viewDidLoad() {
super.viewDidLoad()
addScrollView()
addHeaderView()
let view = UIView()
//anchor view to bottom of the header
}
}
However swift doesn't allow multiple inheritance so I tried using protocols and extensions which works but the problem is that I want other views to be able to be anchored to the header and scrollView so it didn't fit my needs.
What could I do so I can implement something like that
Thanks
Swift does not support multiple inheritance. However, protocols and protocol extensions can accomplish what you want.
Example:
protocol HeaderProtocol {
func addHeaderView() -> UIView
}
extension HeaderProtocol where Self: UIViewController {
func addHeaderView() -> UIView {
let headerView = UIView()
headerView.backgroundColor = UIColor.green
view.addSubview(headerView)
// then anchor it to top
return headerView
}
}
protocol ScrollViewProtocol {
func addScrollView() -> UIView
}
extension ScrollViewProtocol where Self: UIViewController {
func addScrollView() -> UIView {
let scrollView = UIScrollView()
scrollView.backgroundColor = UIColor.green
view.addSubview(scrollView)
// then anchor it to top
return scrollView
}
}
class HomeViewController: UIViewController, ScrollViewProtocol, HeaderProtocol {
override func viewDidLoad() {
super.viewDidLoad()
let scrollView = addScrollView()
let headerView = addHeaderView()
}
}
Alternative approach:
protocol HeaderProtocol {
var headerView: UIView? { get set }
func addHeaderView() -> UIView
}
extension HeaderProtocol where Self: UIViewController {
func addHeaderView() -> UIView {
let headerView = UIView()
headerView.backgroundColor = UIColor.green
view.addSubview(headerView)
// then anchor it to top
return headerView
}
}
protocol ScrollViewProtocol {
var scrollView: UIView? { get set }
func addScrollView() -> UIView
}
extension ScrollViewProtocol where Self: UIViewController {
func addScrollView() -> UIView {
let scrollView = UIScrollView()
scrollView.backgroundColor = UIColor.green
view.addSubview(scrollView)
// then anchor it to top
return scrollView
}
}
class HomeViewController: UIViewController, ScrollViewProtocol, HeaderProtocol {
var scrollView: UIView?
var headerView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
scrollView = addScrollView()
headerView = addHeaderView()
}
}

How can I add a UITableView as a subview to a UIView?

First, this is possible and is this a good idea?
I don't want the UITableView to fill up the whole controller and I want to control the location of this view as I am learning. I have tried searching for tutorials showing this, but I am coming up empty handed as I have tried to implement this functionality.
So, I have a ChildView that subclasses UIView. This view implements or conforms to the necessary UITableView protocols such that I can set the delegate and dataSource to the ChildView. However, I can't seem to make it work properly. Can someone perhaps help me answering why? This is my first time working with UITableViews, so I don't have much experience yet. Working on it! :)
class ChildView: UIView
{
let childTableView = UITableView()
override init(frame: CGRect)
{
super.init(frame: frame)
childTableView.register(ChildTableViewCell.self, forCellReuseIdentifier: "cellId")
childTableView.delegate = self
childTableView.dataSource = self
childTableView.translatesAutoresizingMaskIntoConstraints = false
addSubview(childTableView)
childTableView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
childTableView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
childTableView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
childTableView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
setupLayout()
}
private func setupLayout()
{
self.layer.cornerRadius = 13
self.backgroundColor = UIColor.white
self.layer.borderWidth = 2
self.layer.borderColor = UIColor.black.cgColor
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
extension ChildView: UITableViewDataSource, UITableViewDelegate
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
return tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
}
}
class ChildTableViewCell: UITableViewCell
{
override init(style: UITableViewCellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
private func setupViews()
{
// Nothing yet.
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Edit 1: Included an image of the view that I am trying to fill with a UITableView.
Edit 2: I have added the code that makes it work in the ChildView class.
From an architectural point of view, you shouldn't do this. A view should only display data that it get's from somewhere outside. It should not act as a data source and try to get it's own data. If somebody reads your code (imaging yourself in one year), no-one would expect a view to behave in the way you implemented it.
Then, you do not display the table view anywhere. You'll have to add it as a subview to self and setup its frame / autolayout constraints to react on resizing etc.
If you want to separate the data source code from the view controller, you should create your own class which implements UITableViewDataSource, instantiate it, set it as the data source to the table view, and there you go. Separating the delegate would work the same, but maybe it is better to keep it in the view controller, because this acts as a co-ordinator between all the views in it.
Here is Sample code Posted , You can check this out Adding two tableViews in Different Views as Subview , used parent view is a ContainerView but you cause same code and just add subview in Your Normal UIView
Reference Code :
/// Class Obj
private lazy var FirstObject: firstVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "firstVC") as! firstVC
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
/// Adding it as Subview:
private func add(asChildViewController viewController: UIViewController)
{
// Configure Child View
viewController.view.frame = CGRect(x: 0, y: 0, width: self.firstContainer.frame.size.width, height: self.firstContainer.frame.size.height)
// Add Child View Controller
addChildViewController(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = true
// Add Child View as Subview
firstContainer.addSubview(viewController.view)
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
GitHub Repo - https://github.com/RockinGarg/Container_Views.git

Selecting collection view cell programmatically cannot do some of the cell's isSelected routines

I have this code to change the color and font style of a cell when it is selected. It worked except that when I select it manually from the parent view's init(frame:) function. It supposed to change the color to blue and font style to bold and it worked perfectly when I toggled it from the test device. However, when I tried to set the default selected item programmatically, it only changed the style to bold but the color is still the same as unselected, ie. gray.
This is the cell's class:
class ItemCollectionCell: UICollectionViewCell {
#IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setupCell(title: String)
{
titleLabel.text = title
titleLabel.textColor = UIColor.gray
}
override var isSelected: Bool{
didSet {
titleLabel.textColor = isSelected ? UIColor.blue : UIColor.gray
titleLabel.font = isSelected ? UIFont.boldSystemFont(ofSize: titleLabel.font.pointSize) : UIFont.systemFont(ofSize: titleLabel.font.pointSize)
}
}
}
The parent view that has the collection view in it:
class ItemsView: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
let reuseableCellId = "itemCell"
let nibItemCell = UINib(nibName: "ItemCollectionCell", bundle: nil)
//Collection view for items
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
//Create subview (collection) for the menu buttons
//Register cell's XIB and class
cv.register(nibItemCell, forCellWithReuseIdentifier: reuseableCellId)
//Set subview BG color
cv.backgroundColor = UIColor.white
cv.contentInsetAdjustmentBehavior = .never
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
//Add subview
self.addSubview(collectionView)
//Set subview constraints
self.addConstraintsWithFormat("H:|[v0]|", views: collectionView)
self.addConstraintsWithFormat("V:|[v0]|", views: collectionView)
let selectedPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedPath, animated: false, scrollPosition: .centeredVertically)
}
//... all the other funcs
}
What went wrong here? How do I fix this? Thanks.
It doesn't look like the collectionview is reloaded prior to selecting the cell. Plus, have you tried manually updating isSelected?
After meddling with the code myself, I found out that the setupCell(title:) function is the culprit. FYI, the setupCell is called in the cellForItemAt() delegate function. And when I moved the titleLabel.textColor = UIColor.gray line to awakeFromNib() it works fine now. Thanks anyway guys.

Xcode 8/Swift 3 Breakpoint 2.5 in outlet

Why am I getting a breakpoint 2.5 on my outlet named scrollPages? I'm using it to make let the user scroll up and down but for some reason I'm getting breakpoint 2.5.
I didn't get the breakpoint before when I tried the app, then I added a feature that didn't work then I it and now when I removed the feature I still get it.
Here is my code and the outlet that gives me the breakpoint has a --> next to it. Thanks!
// ViewController.swift
import UIKit
class ViewController: UIViewController, UIApplicationDelegate {
--> #IBOutlet weak var scrollPages: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let V1 : View1 = View1(nibName: "View1", bundle: nil)
let V2 : View1 = View1(nibName: "View2", bundle: nil)
self.addChildViewController(V1)
self.scrollPages.addSubview(V1.view)
V1.didMove(toParentViewController: self)
self.addChildViewController(V2)
self.scrollPages.addSubview(V2.view)
V1.didMove(toParentViewController: self)
var V2Frame : CGRect = V2.view.frame
V2Frame.origin.y = self.view.frame.height
V2.view.frame = V2Frame
self.scrollPages.contentSize = CGSize(width: self.view.frame.width,
height: self.view.frame.height * 2)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Swift Adding Subview with UITableView Press

I'm very new to swift so sorry if this is a basic question or I'm doing something terribly wrong. I've been having some issues trying to add a subview when touching a row on a tableview and have been working off of this page: http://myxcode.net/2015/11/07/adding-a-subview-using-a-xib-and-storyboard/
Here's the relevant code I have so far (I removed some of the tableview logic because that works fine):
Subview Class
class ConfirmTeamView: UIView, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var teamListTableView2: UITableView!
#IBOutlet weak var confirmButton2: UIButton!
#IBOutlet weak var cancelButton2: UIButton!
var playerList: [Player]?
var view: UIView!
init(pList: [Player]) {
self.playerList = pList
super.init(frame: CGRectMake(20, 100, 385, 339))
setup()
teamListTableView2.delegate = self
teamListTableView2.dataSource = self
let playerListTableCellNib = UINib(nibName: "PlayerListTableViewCell", bundle: nil)
teamListTableView2.registerNib(playerListTableCellNib, forCellReuseIdentifier: "PlayerListTableViewCell")
}
required init?(coder aDecoder: NSCoder) {
self.playerList = nil
super.init(coder: aDecoder)
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleHeight,UIViewAutoresizing.FlexibleWidth]
addSubview(view)
}
func loadViewFromNib () -> UIView
{
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "ConfirmTeamView", bundle: bundle)
//this line I think is causing a stack overflow, any idea why?
let thisview = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return thisview
}
}
and then in my viewController (I again removed some tableview methods for ease of reading):
class ChooseExistingTeamViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var TeamListTableView: UITableView!
var existingTeamsArray = [Team]()
var confirmTeamUI : ConfirmTeamView!
override func viewDidLoad() {
super.viewDidLoad()
TeamListTableView.delegate = self
TeamListTableView.dataSource = self
let testPlayer1 = Player(name: "John", number: 24)
let testPlayer2 = Player(name: "Smith", number: 50)
let testTeam = Team(name: "myTeam", players: [testPlayer1, testPlayer2])
existingTeamsList = [testTeam]
}
override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// This is where I load the subview
TeamListTableView.userInteractionEnabled = false
let selectedTeam = existingTeamsList[indexPath.row]
let confirmTeamUI = ConfirmTeamView(pList: selectedTeam.players)
confirmTeamUI.cancelButton2.addTarget(self, action: "cancelPressed:", forControlEvents: UIControlEvents.TouchUpInside)
let viewWidth = self.view.frame.width
let xWidth = viewWidth - 40
let yHeight = 200
confirmTeamUI.frame = CGRect(x: 20, y: 100, width: Int(xWidth), height: yHeight)
self.view.addSubview(confirmTeamUI)
}
func cancelPressed(sender: UIButton) {
// self.confirmTeamUI.view.removeFromSuperview() [This line throws an exception for unwrapping an optional value]
if self.confirmTeamUI != nil { self.confirmTeamUI.removeFromSuperview() }
else { print("Confirm Team is nil") }
// pressing the cancel button runs the else case
}
Any advice would be very appreciated and please let me know if you need more!

Resources