UITableViewCell holding UICollectionView and click collection cell segue - ios

I am using Swift3 and Xcode 8.3.3
I am working on UITableViewCell which hold UICollectionView.
Each CollectionView cell need to perform segue.
I mean, i need to call other ViewController with Navigation Controller. So user can back to Main UITableViewController.
import UIKit
class PastCell: UITableViewCell {
#IBOutlet weak var ptripDtls: UICollectionView!
var logcount:Int = 0
override func awakeFromNib() {
super.awakeFromNib()
ptripDtls.delegate = self
ptripDtls.dataSource = self
logcount = Transfer.tripObj[Transfer.cellpos]["logcount"]! as! Int
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let width = self.ptripDtls.collectionViewLayout.collectionViewContentSize.width;
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: width-17 , height: 50)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 1
ptripDtls!.collectionViewLayout = layout
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
extension PastCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return logcount
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PastTripCollectionCell", for: indexPath) as! PastTripCollectionCell
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Might be here segue function will call
}
}

You have actually multiple choices here:
Closure
Pass a closure inside the UITableViewCell like tapped: (Model) -> Void when you're dequeuing a tableView cell, and specify inside that closure to perform a segue. (it works if all your collection cells will perform a same segue, you can use this mechanism to propagate your callback if different segue's should get called)
Delegate
Define a custom delegate, to inform your viewController about CollectoinView's DidSelectItem method, and then perform Segue accordingly.
Notification
Trigger a Notification throughout the system which only your ViewController listens to that notification, and perform your segue accordingly. (This isn't actually a good solution, just pointing out that it's possible)

For swift 4 Use perform segue inside didSelectRowAtIndexPath if you are using segue
self.performSegue(withIdentifier: "YourSegueIdentifier", sender: nil)
and use Push if you are using only navigation controller.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "YourVCIdentifier") as! YourVC
self.navigationController?.pushViewController(vc, animated: true)

Related

I really don't know how to make collectionViewCell (didSelected) function work properly

I'm making a Calendar in Swift 5 and now I'm encountering a problem.
First this is my code:
import UIKit
class YearViewController: UIViewController {
#IBOutlet weak var yearName: UILabel!
#IBOutlet weak var yearCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
yearName.text = String(CalenderBrain.init().curruntYear)
yearCollectionView.dataSource = self
yearCollectionView.delegate = self
yearCollectionView.register(UINib(nibName: "ReusableYearCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "MyCell3")
if let layout = self.yearCollectionView.collectionViewLayout as? UICollectionViewFlowLayout{
layout.minimumInteritemSpacing = -10
layout.minimumLineSpacing = 1
}
}
}
extension YearViewController : UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell3", for: indexPath) as! ReusableYearCollectionViewCell
cell.monthName.text = Calendar.current.monthSymbols[indexPath.row]
if cell.monthName.text == CalenderBrain.init().curruntMonthName(){
cell.monthName.textColor = .red
cell.tag = 999
}
cell.month = indexPath.row + 1
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: 126.3, height:(570-3)/4)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "lastOne") as? ViewController
vc?.whatIGetFromYearViewController = indexPath.row + 1
vc?.curruntMonthNameThatIHaveToPut = Calendar.current.monthSymbols[indexPath.row]
vc?.nextMonthNameThatIHaveToPut = Calendar.current.monthSymbols[indexPath.row + 1]
self.navigationController?.pushViewController(vc!, animated: true)
print("i'm pressed")
}
}
I want if my cells are clicked then a segue occurs.
And this is an image of my cell. (I make collectionView in collectionViewCell.)
The problem is the segue only occurs when I click the left part of the label. If I click collectionView in CollectionViewCell then the segue doesn't occur. I think that's because Swift recognizes collectionView in collectionViewCell another CollectionView. So for now, I want to make collectionViewCell clicked and segue occurred regardless of part that I click. Is that possible?
Try set collectionView.isUserInteractionEnable = false in collectionViewCell.
This code will prevent user action onto collectionView in collectionViewCell, just receive didSelectItem action of main collectionView.

UIViewController still in memory after calling dismiss

So I create a UICollectionViewController class along with a custom UICollectionViewCell. I am presenting a new UIViewController from the CollectionView and then dismissing it in the UIViewController in order to return to the CollectionView. Everything works except for the fact that after dismissing the UIViewController it still remains in memory which is not what I want. I would like to completely destroy the UIViewController once it is dismiss but cannot figure out how do it.
Am I doing anything wrong? Is it normal for the dismiss ViewController to remain in memory after it's dismissal?
// UICollectionViewController class
class MyCollection: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
override func viewDidLoad() {
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: cellId)
}
let viewControllers:[UIViewController] = [ViewController1()]
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewControllers.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let activityCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell
return activityCell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = viewControllers[indexPath.item]
present(vc, animated: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 90)
}
}
// Custom UICollectionViewCell class
class CustomCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController1: UIViewController {
lazy var dismissButton: UIButton = {
let newButton = UIButton(type: .system)
newButton.setTitle("Dismiss", for: .normal)
newButton.backgroundColor = .red
newButton.addTarget(self, action: #selector(dismissView), for: .touchUpInside)
newButton.translatesAutoresizingMaskIntoConstraints = false
return newButton
}()
#objc func dismissView() {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(dismissButton)
NSLayoutConstraint.activate([
dissmissButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
dissmissButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
Reason why ViewController1 instance is not destroyed completely?
Even after the viewController is dismissed, you're still holding the reference to it inside MyCollection's viewControllers array.
Solution:
In case, you want a brand new instance of the controller everytime a cell is tapped, there is no need to store the controller in viewControllers array.
Simply update didSelectItemAt method to,
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = ViewController1() //here......
present(vc, animated: true, completion: nil)
}
No you are not doing it wrong. There is no strong retain cycle within your code.
The only problem is, even after you dismiss your view controller, it still resides in here
let viewControllers:[UIViewController] = [ViewController1()]
If you want the instance to be destroyed completely you need to remove it from the array as well.
It's not a good choice to make a global variable of a viewController.Once you still hold it,you'll be unable to destroy it.Do it like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc:UIViewController?
switch indexPath.row {
case 0:
vc = ViewController1()
case 1:
vc = ViewController2()
default:
vc = ViewController0()
}
present(vc!, animated: true, completion: nil)
}
And if there are many viewControllers, maybe it will be better to create a Array of Class like this:
guard let nameSpace = Bundle.main.infoDictionary?["CFBundleName"] as? String else { return }
let clsName = String(format: "%#.%#", nameSpace, cList[indexPath.row])
let cls = (NSClassFromString(clsName) as? UIViewController.Type)!
let vc = cls.init()
present(vc, animated: true, completion: nil)
clist:let cList = ["FirstController","SecondController"]
PS:
Of course I would not use this route if I get 50 ViewControllers.I just think that we can just use the most convenient way to solve the problem.
Hope this will help you.

How to open activity and pass variable on selection of collectionview cell?

I have a collectionView and I am using a custom cell that shows an Image and label. I am populating the view with an array. When a cell is selected, I want a new activity to open and the name of the class to be passed through.
Here is my code:
class CollectionViewController: UICollectionViewController {
let classes = ["English","Math","Science","Social Studies","Other","Technology"]
let class_images : [UIImage] = [
UIImage(named: "English")!,
UIImage(named: "Math")!,
UIImage(named: "Science")!,
UIImage(named: "Social Studies")!,
UIImage(named: "Other")!,
UIImage(named: "Technology")!
]
override func viewDidLoad() {
super.viewDidLoad()
var layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionInset = UIEdgeInsets(top: 22, left: 22, bottom: 22, right: 22)
layout.minimumInteritemSpacing = 22
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return classes.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "class_cell", for: indexPath) as! custom_class_cell
cell.class_name.text = classes[indexPath.item]
cell.class_image.image = class_images[indexPath.item]
// Configure the cell
cell.layer.borderWidth = 1.0
cell.layer.borderColor = UIColor.black.cgColor
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//This isn't the right code, but an example of what I want to do!
if (indexPath=1){
let vc = self.storyboard?.instantiateViewController(withIdentifier:
"classes")
self.present(vc!, animated: true, completion: nil)
//I want to pass this string to the class
let class_name2 = "English"
}
else if(indexPath=2){
let vc = self.storyboard?.instantiateViewController(withIdentifier:
"classes")
self.present(vc!, animated: true, completion: nil)
//I want to pass this string to the class
let class_name2 = "Math"
//it keeps going through the technology cell
}
}
In the didSelectItemAt method, there is an example of what I am trying to do, but the code isn't right. I want to do this for all cells English to Technology. Thank you in advance and let me know if you have any questions!
easiest way:
in dest controller (let's say is a DetailController instance)
you should have:
class DetailController...
... var myInfo : MyInfo?
(MyInfo should contain ALL data You want to pass.)
and in prepare for segue:
vc.myInfo = Info(class_name2)
in viewDidLoad populate your UI:
override func viewDidLoad() {
super.viewDidLoad()
self.detail.text = self. myInfo....
It is actually a really simple solution, I just over complicated it!
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let valueToPass = classes[indexPath.row]
That gets you name of each class when the cell is clicked. After that, just do the prepare for segue method.

Singleton Class not updating properly

I've got a collectionViewController and a normal viewcontroller. When a cell is tapped it goes to the VC and sets the label to the cell tapped. The variable for this is in a singleton class.
The issue I'm having is that the first time you tap a cell and go to the VC the label doesn't say anything (console prints correct data though). Then you go back to the collectionView and tap a different cell, the label in the view now shows the cell you tapped previously.
I tried cleaning the build folder etc. but didn't do anything. I also tried another method - let CVC = CollectionViewController() then lbl.text = CVC.cellTapped (create var first) but that didnt work either.
SharingManager.swift
class SharingManager {
var cellChoose = String()
static let sharedInstance = SharingManager()
}
CollectionViewController.swift
class CollectionViewController: UICollectionViewController {
let sm = SharingManager.sharedInstance
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// Configure the cell
let lbl = cell.viewWithTag(1) as! UILabel
lbl.text = String(indexPath.row)
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
sm.cellChoose = "cell\(indexPath.row)"
print(sm.cellChoose)
}
}
VC2.swift (the viewcontroller tapping a cell takes you to)
class VC2: UIViewController {
let sm2 = SharingManager.sharedInstance
#IBOutlet weak var lbl2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.lbl2.text = sm2.cellChoose
//print(sm2.cellChoose)
}
Since you're navigating via a segue, it's possible your new ViewController is loaded before didSelectItem is called. Moving lbl2.text = sm2.cellChoose to viewWillAppear will fix your issue.
The "correct" way to do this with segues is handle this in prepareForSegue, but what you have now will work.

didSelectItemAt not working from SCLAlertView

I am using SCLAlertView to create custom alert view. My alert view contains one text field and collection view of coloured cells
Problem is that UICollectionView's didSelectItemAt method is not working. I think problem is because it is like subview. But I can't fix it.
I have one collection view at UIViewController and that method is working. Here's my code
var collectionViewAlert: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
layout.itemSize = CGSize(width: 25, height: 25)
collectionViewAlert = UICollectionView(frame: CGRect(x: 18, y: 10, width: 250, height: 25), collectionViewLayout: layout)
collectionViewAlert.dataSource = self
collectionViewAlert.delegate = self
collectionViewAlert.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CollCell")
collectionViewAlert.backgroundColor = UIColor.white
}
#IBAction func addCategory(_ sender: Any) {
let alertView = SCLAlertView()
alertView.addTextField("Enter category name")
let subview = UIView(frame: CGRect(x:0,y:0,width:216,height:70))
subview.addSubview(self.collectionViewAlert)
alertView.customSubview = subview
alertView.showEdit("Choose color", subTitle: "This alert view has buttons")
}
let reuseIdentifier = "cell" // also enter this string as the cell identifier in the storyboard
var colors = [UIColor.red, UIColor.yellow, UIColor.green, UIColor.blue, UIColor.cyan]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.colors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
if (collectionView == self.collectionViewAlert) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollCell", for: indexPath as IndexPath)
cell.backgroundColor = self.colors[indexPath.item]
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath)
cell.backgroundColor = self.colors[indexPath.item]// make cell more visible in our example project
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
}
}
More screens here: screens
EDIT:
I still not found answer how to solve this problem. I think problem is subview interaction, because delegate method cellForItemAt is invoked on alert show. Someone know how to figure this out? screen from view hierarchy
Thanks for any help.
I have looked into SCLAlertView code. It seems it uses a tap recognizer for dismissing the keyboard.
Tap recognizer can conflict with the tap recognizer used by collection view.
To disable the recognizer in SCLAlertView you can use an appearance object:
let appearance = SCLAlertView.SCLAppearance(
disableTapGesture: true
)
let alertView = SCLAlertView(appearance: appearance)
You can also add extension for CollectionView delegate:
extension ViewController: UICollectionViewDelegate
{
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
}
}
If you have added any UIImageView or UILabel inside UICollectionViewCell make sure you have enabled UserIntraction, because for both UIImageView or UILabel default as false.
setUserIntraction for both UIImageView or UILabel as TRUE.
You need to add collectionview delegate in protocol section. And be sure that you have make outlets of your object.
First of all you need to update like:
class YourViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
}
And then in viewDidLoad:
collectionView.delegate = self
collectionView.dataSource = self
collectionView.reloadData()
And make sure that your objects are properly connected.

Resources