Swift 4: Passing a string in UICollectionViewCell to UICollectionViewController - ios

I am trying to pass this string from one UICollectionViewCell to UICollectionViewController. I would like the "Let's Get Started!" to go on my navigation title... Below is my code and I can't figure out why the string isn't getting passed.
// UICOLLECTIONVIEWCELL --> This is the first UICollectionViewCell
#objc func getStartedAction() {
let confirmingTapActionButton1 = "Let's Get Started!"
let signUpFlowController1 = SignUpFlowController()
signUpFlowController1.welcomeCellPassedStringForAction1 = confirmingTapActionButton1
}
// UICollectionViewController --> This is the second UICollectionViewController
class SignUpFlowController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var welcomeCellPassedStringForAction1: String? = nil
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
// NAV BAR STUFF BELOW
self.title = welcomeCellPassedStringForAction1
}

You can do this using a protocol
Make a protocol:
protocol NavTitleProtocol{
func setNavTitle(title: String)
}
Conform your CollectionViewController to the protocol and override the setNavTitle method:
extension YourCollectionViewController: NavTitleProtocol{
func setNavTitle(title: String) {
self.title = title
}
}
In your cell, have a delegate property of type NavTitleProtocol:
class YourCollectionViewCell: UICollectionViewCell{
var delegate: NavTitleProtocol?
#objc func getStartedAction() {
let confirmingTapActionButton1 = "Let's Get Started!"
// let signUpFlowController1 = SignUpFlowController()
// signUpFlowController1.welcomeCellPassedStringForAction1 = confirmingTapActionButton1
delegate?.setNavTitle(title: confirmingTapActionButton1)
}
}
Assign your collectionViewController as the delegate when you create the collectionView cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourIdentifier", for: indexPath) as! YourCollectionViewCell
cell.delegate = self
}
When you perform the selector in your cell, the delegate property will be accessed and the method that you have overriden in your CollectionViewController will be called.

First this line
let signUpFlowController1 = SignUpFlowController()
creates a new instance other than the shown one , so you have to use the delegate to catch the presented instance
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
cell.myInstance = self
}
class customCell:UICollectionViewCell {
var myInstance:SignUpFlowController!
#objc func getStartedAction() {
let confirmingTapActionButton1 = "Let's Get Started!"
myInstance.welcomeCellPassedStringForAction1 = confirmingTapActionButton1
}
}

Related

Passing value from collectionView to new VC - Property initializers run before "self" is available (Swift 5)

Trying to pass a value selected in a collectionView into a new View Controller to determine which local json file to decode. Get the error "Cannot use instance member 'selectedCategory' within property initializer; property initializers run before 'self' is available"
Initial VC:
extension MenuViewController: UICollectionViewDataSource {
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedVC = storyboard?.instantiateViewController(withIdentifier: "SelectImageViewController") as! SelectImageViewController
selectedVC.selectedCategory = categories[indexPath.row].category
navigationController?.pushViewController(selectedVC, animated: true)
}
}
Second VC:
class SelectImageViewController: UIViewController {
lazy var selectedCategory: String = ""
var selectedArray = Bundle.main.decode([Images].self, from: "\(selectedCategory).json")
override func viewDidLoad() {
super.viewDidLoad()
...
}
I tried the lazy var and init methods suggested in other posts with this error, but no luck.
You cannot run code which refers to self outside of a method (unless it's declared as lazy)
It's simpler to decode the array before presenting the controller
extension MenuViewController: UICollectionViewDataSource {
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedVC = storyboard?.instantiateViewController(withIdentifier: "SelectImageViewController") as! SelectImageViewController
let selectedCategory = categories[indexPath.row].category
selectedVC.selectedArray = Bundle.main.decode([Images].self, from: "\(selectedCategory).json")
navigationController?.pushViewController(selectedVC, animated: true)
}
}
...
class SelectImageViewController: UIViewController {
var selectedArray = [Images]()
override func viewDidLoad() {
super.viewDidLoad()
...
}
Or if you really want to decode the array in SelectImageViewController
extension MenuViewController: UICollectionViewDataSource {
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedVC = storyboard?.instantiateViewController(withIdentifier: "SelectImageViewController") as! SelectImageViewController
selectedVC.selectedCategory = categories[indexPath.row].category
navigationController?.pushViewController(selectedVC, animated: true)
}
}
...
class SelectImageViewController: UIViewController {
var selectedCategory = ""
var selectedArray = [Images]()
override func viewDidLoad() {
super.viewDidLoad()
selectedArray = Bundle.main.decode([Images].self, from: "\(selectedCategory).json")
...
}

The delegate pattern does not fire in the class where it is implemented

There is a TopMenuBar class. This is a menu bar (collectionView) for a tableViewCell in the main class (named - MainVC).
class TopMenuBar: UITableViewCell {
public var delegate: TopMenuBarDelegate?
}
and protocol:
protocol TopMenuBarDelegate {
func didTapTopMenuCell(named: String)
}
In the didSelectItemAt() method with delegate, I call this protocol:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
///Relay to delegate about menu item selection
let selectedItem = menuItems[indexPath.row + 1].description
delegate?.didTapTopMenuCell(named: selectedItem)
}
Further, in the main class - MainVC, I subscribed to the protocol - TopMenuBarDelegate, added a protocol method - didTapTopMenuCell(), created an instance of the TopMenuBar class and in the viewDidLoad method I assigned a delegate to this protocol - the MainVC class.
class MainVC: UIViewController, TopMenuBarDelegate {
let topMenuBar = TopMenuBar()
func didTapTopMenuCell(named: String) {
print(named)
}
override func viewDidLoad() {
super.viewDidLoad()
topMenuBar.delegate = self
}
}
To test the functionality, I only want to print the name of the cell that I clicked on.
As a result, nothing appears in the console.
You're not using your topMenuBar.
let topMenuBar = TopMenuBar()
In viewDidLoad you do topMenuBar.delegate = self, but the actual view that you set as the header is this:
guard let cell = tableView.dequeueReusableCell(withIdentifier: TopMenuBar.cellIdentifier()) as? TopMenuBar else {
return nil
}
Here, you're creating a new TopMenuBar (cell)! You're not using the let topMenuBar = TopMenuBar() at all!
What you need to do is get rid of:
let topMenuBar = TopMenuBar()
override func viewDidLoad() {
super.viewDidLoad()
topMenuBar.delegate = self
}
and instead, set the delegate of cell.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TopMenuBar.cellIdentifier()) as? TopMenuBar else {
return nil
}
cell.delegate = self /// assign here!
return cell
}
Rather than placing the topMenuBar.delegate = self in the viewDidLoad, try putting it in the cellForItemAt when you initialize the collectionView in the MainVC.

change variables from an other view controller when a textField is changed

I'm trying to change the values of a variable in two different view controllers from the value of a textField but I don't understand how to use the delegate so that it works.
My Storyboard:
My Code:
MainView:
class GameCreatingViewController: UIViewController {
var newGame = Game()
override func viewDidLoad() {
super.viewDidLoad()
newGame = Game()
newGame.playerBook.NumberOfPlayers = 2
if let vc = self.children.first(where: { $0 is PlayersTableViewController }) as? PlayersTableViewController {
vc.currentGame = self.newGame
vc.tableView.reloadData()
}
if let vc = self.children.first(where: { $0 is GameViewController }) as? GameViewController {
vc.currentGame = self.newGame
}
}
func changeName(name: String, number: Int) {
self.newGame.playerBook.players[number].name = name
}
}
tableViewController:
class PlayersTableViewController: UITableViewController, UITextFieldDelegate {
var currentGame = Game()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}
func changeName(name: String, number: Int) {
self.currentGame.playerBook.players[number].name = name
}
}
The Cell:
protocol changeNameDelegate: class {
func changeName(name: String, number: Int)
}
class PlayerNameTableViewCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: changeNameDelegate? = nil
#IBOutlet weak var playerName: UITextField!
var numberOfPlayer: Int = Int()
#IBAction func changeName(_ sender: UITextField) {
delegate?.changeName(name: sender.text!, number: numberOfPlayer)
}
}
It seems like the action from the button executes but the fonctions from the other viewcontrollers don't.
Use the delegate to notify the other viewController.
Make sure isn't nil.
Usually protocols name the first letter is capitalized.
A good practice is to implement protocols in extensions.
Implement the changeNameDelegate protocol.
class PlayersTableViewController: UITableViewController, UITextFieldDelegate, changeNameDelegate {
And in the cell configuration set the delegate.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
cell.delegate = self // This line is missing.
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}

Pass coredata to ViewController based on cell selection

I need to present a new ViewController when selecting a UICollectionView Cell and pass the data from the entity used to fill selected cell.
Here is the code used to fill cell data:
let pets = PersistenceManager.shared.fetch(Pet.self)
var _fetchResultsController: NSFetchedResultsController <Pet>?
var fetchResultsController: NSFetchedResultsController <Pet>?{
get{
if _fetchResultsController == nil {
let moc = PersistenceManager.shared.context
moc.performAndWait {
let fetchRequest = PersistenceManager.shared.petsFetchRequest()
_fetchResultsController = NSFetchedResultsController.init(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil) as? NSFetchedResultsController<Pet>
_fetchResultsController?.delegate = self
do {
try self._fetchResultsController?.performFetch()
}catch {
}
}
}
return _fetchResultsController
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionViewHorizontal.dequeueReusableCell(withReuseIdentifier: "HorCell", for: indexPath) as! PRMainHorizontalCollectionViewCell
if let pet= self.fetchResultsController?.fetchedObjects, indexPath.row < pet.count{
let _pet= fetchResultsController!.object(at: indexPath)
// cell UI goes here
}
return cell
}
I understand I need to use didSelectItemAt, I just don't know what information needs to go in the function. Please let me know of anything else needed to better help answer this question. Thank you.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Added the line below based on karthik's answer. But I am unsure how to implement it.
let selectedObj = fetchResultsController!.object(at: indexPath)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "selectedPetViewController") as! PRSelectedPetViewController
navigationController?.pushViewController(vc, animated: true)
}
I prefer the following architecture:
This is the main controller with data.
For a better understanding, I will simplify the data source.
class ViewController: UIViewController {
// code ...
#IBOutlet var collectionView: UICollectionView!
fileprivate var data = [Pet]()
}
extension ViewController: UICollectionViewDataSource {
// code ...
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorCell", for: indexPath) as? PRMainHorizontalCollectionViewCell else {
return UICollectionViewCell()
}
let pet = data[indexPath.row]
// TODO: configure cell using pet ...
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let row = indexPath.row
let pet = data[row]
// TODO: Get the child controller in any way convenient for you.
let childController = ChildViewController()
// With the help of the delegate, we will receive notification of changes pet.
childController.delegate = self
// Thus, we pass the data to the child controller.
childController.pet = pet
childController.indexPath = indexPath
// TODO: Present the view controller in any way convinient for you.
}
}
extension ViewController: ChildViewControllerDelegate {
func saveButtonPressed(_ controller: ChildViewController) {
guard let pet = controller.pet, let indexPath = controller.indexPath else {
return
}
// We save data and reload the cell whose data we changed.
self.data[indexPath.row] = pet
collectionView.reloadItems(at: [indexPath])
}
func cancelButtonPressed(_ controller: ChildViewController) {
// Do something if necessary...
}
}
In addition to the controller, the child controller also provides a delegate protocol for notification of changes.
protocol ChildViewControllerDelegate {
func saveButtonPressed(_ controller: ChildViewController)
func cancelButtonPressed(_ controller: ChildViewController)
}
// This is the controller you want to show after selecting a cell.
// I assume that in the child controller there is a button to save and cancel.
class ChildViewController: UIViewController {
var delegate: ChildViewControllerDelegate?
// The object whose data we are editing in the controller.
var pet: Pet!
// The location of the object in the main controller.
var indexPath: IndexPath!
override func viewDidLoad() {
// TODO: Configure user interface using self.pet
}
#IBAction func saveButtonPressed(_ button: UIButton) {
delegate?.saveButtonPressed(self)
}
#IBAction func cancelButtonPressed(_ button: UIButton) {
delegate?.cancelButtonPressed(self)
}
}
you can follow this to pass information to another view controller.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedObj = fetchResultsController!.object(at: indexPath)
// instantiate presenting view controller object
// add one property (manange object) in your presenting viewcontroller
// assign the selected object to that property
// present the view controller
}

Use CollectionView methods from another swift file

I'd like to use a CollectionView methods from another swift file instead of it's ViewController for some reason.
I have this in my ViewController:
#IBOutlet weak var collectionView: UICollectionView!
var broadcastColletionView = BroadcastCollectionView()
override func viewDidLoad() {
super.viewDidLoad()
broadcastColletionView = BroadcastCollectionView(eventItems: eventItems,collectionView: collectionView, broadastObject: broadastObject)
collectionView.dataSource = broadcastColletionView
collectionView.delegate = broadcastColletionView
}
And I have BroadcastCollectionView.swift which contains the CollectionView delegate methods:
class BroadcastCollectionView: NSObject,UICollectionViewDelegate, UICollectionViewDataSource {
var eventItems = [Eventtype]()
var alreadyChecked: Bool = false
var cellHistory: IndexPath = []
var collectionView: UICollectionView!
var broadastObject = Broadcast()
init(eventItems: [Eventtype],collectionView: UICollectionView,
broadastObject: Broadcast) {
self.eventItems = eventItems
self.collectionView = collectionView
self.broadastObject = broadastObject
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return eventItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "brCollectionView", for: indexPath) as! BroadcastCollectionViewCell
self.collectionView = collectionView
cell.eventImage.image = eventItems[indexPath.row].image
cell.eventType = eventItems[indexPath.row]
let tap = UITapGestureRecognizer(target: self, action: #selector(collectionViewTapped))
tap.numberOfTapsRequired = 1
cell.addGestureRecognizer(tap)
return cell
}
#objc func collectionViewTapped(sender: UITapGestureRecognizer) {
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell : BroadcastCollectionViewCell = collectionView.cellForItem(at: indexPath)! as! BroadcastCollectionViewCell
print("item index")
} else {
print("collection view was tapped")
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selected row is",indexPath.row)
}
I don't really understand why the delegate methods not called if I setted the collectionView.delegate and dataSource to the BroadcastCollactionViewclass. Please don't make me explain why would I like to separate this CollectionView it's not part of the question.
After setting datasource and delegate for the collection view in viewDidLoad() method, reload the collection view:
collectionView.reloadData()
This another class that you created is called CustomDataSource you can find a tutorial here
Try calling
collectionView.reloadData() after setting the dataSource and delegate.
and Make sure eventItems is not empty.
Hope it Helps!
Apart from the issue of not working in your case which could be due to not configuring the collection view properly which can be resolved using the answers you got above from other users, There is an approach for your question of "Use CollectionView methods from another swift file"
You can make use of the concept called "extension", I will explain you how.
Create a new class for handling the collection view methods as follows,
class MyDataSource: NSObject {
var eventItems: Array<Any>?
init(events: Array<Any>?) {
self.eventItems = events
}
}
extension MyDataSource: UITableViewDataSource, UITableViewDelegate {
// MARK: - Table view data source methods
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier")
return cell
}
// Add additional data source methods & delegate methods as per your need
// MARK: - Table view delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Perform your action
// Use delegate methods(protocols) to do your actions from the viewController class
}
}
And in the viewController file assign the datasource & delegate method for collection view as follows,
class ViewController: UIViewController {
var datasource: MyDataSource?
override func viewDidLoad() {
super.viewDidLoad()
// Initialize datasource & delegate for collectionView with the extension datasource
datasource = MyDataSource(events: EVENTITEMS_ARRAY)
collectionView?.dataSource = datasource
collectionView?.delegate = datasource
}
}
NOTE
Update the data types & collection view properties like cell identifier as per your need.

Resources