I cannot able to pass data with using protocol in swift - ios

I have a xibs, in my homeVC, I m passing a viewModel in didselectItem in collectionView's method. After that, I m navigating detailVC and I assign my delegate to self there but I could not my print data
HomeVC
protocol HomeViewControllerDelegate {
func didTappedIndex(viewModel: HomeCollectionViewCellViewModel)}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let game = games[indexPath.row]
self.delegate?.didTappedIndex(viewModel: HomeCollectionViewCellViewModel(with: game))
self.tabBarController?.navigationController?.pushViewController(DetailViewController(nibName: "DetailViewController", bundle: nil), animated: true)
}
DetailVC
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
HomeViewController().delegate = self
}
}
extension DetailViewController: HomeViewControllerDelegate {
func didTappedIndex(viewModel: HomeCollectionViewCellViewModel) {
print(viewModel.title)
}
}

when you call
HomeViewController().delegate = self
you are creating a new instance of HomeViewController(). You are probably confusing this instance with one you've created somewhere else. You want to assign the delegate of the correct HomeViewController.
one option would be to add an instance variable and shared function to the HomeViewController that allows it to keep track of its instance:
private static var instance: HomeViewController?
internal class func shared() -> HomeViewController {
guard let currentInstance = instance else {
instance = HomeViewController()
return instance!
}
return currentInstance
}
then you can set the delegate in DetailViewController like this:
HomeViewController.shared().delegate = self
Another option would be to have a Coordinator where you keep track of all view controllers. Then you could pass the DetailViewController the HomeViewController instance.

Related

how to push ViewController from xib tableview Cell?

I have tableView within collectionView and i created using xib. I want to pushViewController when item is selected .I tried pushing the view controller in itemForRowAt method but it's not possible
class SecondTableView: UITableViewCell , UIcollectionViewDelegate ,UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// let vc = DataTableViewController()
// vc.delegate = self
// vc.pushNav()
let memeVC = storyboard?.instantiateViewController(withIdentifier: "MemeViewController") as! MemeViewController
memeVC.imagePassed = image
navigationController?.pushViewController(memeVC, animated: true)
print("item tapped\(indexPath)")
}
}
errors
Use of unresolved identifier 'storyboard'; did you mean 'UIStoryboard'?
Add segue as mentioned in the image and select show
Set segue identifier in your storyboard as mentioned in the image
Add below Protocol:
protocol CollectionViewCellDelegate: class {
func userDidTap()
}
Add below property in your table view class where the collection view delegate returns.
weak var delegate: CollectionViewCellDelegate?
Update delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate.userDidTap()
print("item tapped\(indexPath)")
}
}
confirm CollectionViewCellDelegate delegate to your first view controller & add this method:
func userDidTap() {
performSegue(withIdentifier: "showMemeViewController", sender: nil)
}
Add prepare for segue in your first VC.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showMemeViewController" {
let memeVC: MemeViewController = segue.destination as! MemeViewController
memeVC.imagePassed = image
}
}
Please try this let me know if you faced any other issue. Thanks.
You're trying to access storyboard property in a tableview cell, which isn't there in the first place, it's a UIViewController property not a UITableViewCell property, hence the error, you'll have to delegate your selection to the viewController then let the controller do the segue normally.
You can start doing that via delegates as I mentioned above, for example if you're passing image to the controller you wanna segue to:
protocol ImageTableViewCellDelegate: class {
// pass the the image or any variable you want in the func params
func userDidTap(_ image: UIImage)
}
then you'd add it to the cell and confirm the controller to it
class SecondTableView {
weak var delegate: ImageTableViewCellDelegate?
}
In your controller:
extension MyViewController: ImageTableViewCellDelegate {
func userDidTap(_ image: UIImage){
let memeVC = storyboard?.instantiateViewController(withIdentifier: "MemeViewController") as! MemeViewController
memeVC.imagePassed = image
navigationController?.pushViewController(memeVC, animated: true)
print("item tapped\(indexPath)")
}
}
this should work, let me know if you had any further problems

How to send actions from one ViewController to another ViewController (active together) [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I know this question has been asked so many many times. But here the scenario is changed. I am using MMParallaxView and that looks pretty awesome.
But Now at this point, I want to communicate between these two View controllers. Let me tell you that this github code helps us to make a view like ios Map App. I mean you can have Map (as 1st view controller) and you can have a list of places view controller on top of 1st View Controller. Just same like Map app.
Now I want to click on the UITableView cell and want to navigate on map. For this I know how to catch delegated method. And I am successfully getting of taps on the UItableView cell. But How to send that clicked item data to 1stView Controller so that It can show or mark selected area on Map.
I know this can also be done. But how?
To modify the example app for MMParallaxView...
In ChildBottomViewController.swift
Add this at the top (outside of the class):
protocol CellTapDelegate {
func didSelectRow(indexPath: IndexPath)
}
Add this line at the beginning of the class:
var cellTapDelegate: CellTapDelegate?
Change didSelectRowAt function to:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DispatchQueue.main.async { [weak self] in
self?.cellTapDelegate?.didSelectRow(indexPath: indexPath)
}
}
In MapViewController.swift
Change the class declaration to:
class MapViewController: UIViewController, CellTapDelegate {
and add this function:
#objc func didSelectRow(indexPath: IndexPath) {
print("Delegate got didSelectRow for: \(indexPath)")
}
Then, in SecondViewController.swift
Add this at the end of viewDidLoad():
var mapVC: MapViewController?
var bottomVC: ChildBottomViewController?
for vc in self.childViewControllers {
if vc is MapViewController {
mapVC = vc as? MapViewController
} else if vc is ChildBottomViewController {
bottomVC = vc as? ChildBottomViewController
}
}
// if we found valid child view controllers, set the delegate
if mapVC != nil && bottomVC != nil {
bottomVC?.cellTapDelegate = mapVC
}
Now, selecting a row from the "slide up from bottom" table view will send the selected indexPath to its delegate - the map view controller.
protocol CellTapDelegate {
func didTapOnItem(obj: MyObject)
}
class MyViewControllerContainingTheTableView : UIViewcontroller {
weak var delegate: CellTapDelegate?
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let item = arrayOfObjects[indexpath.row]
self.delegate.didTapOnItem(item)
}
}
//View Controller where you you will have the listner
class FirstViewController : UIViewController {
func setupParalax() {
// you have to modify this according to your need
let vc = MyViewControllerContainingTheTableView()
vc.delegate = self
}
}
extension FirstViewController: CellTapDelegate {
func didTapOnItem(obj: MyObject) {
// you have your required object here
}
}
This should be possible by implementing your own delegate, but my knowledge of MMParrallaxView is that there is only 1 view controller which displays two views meaning the view controller should be able to pass things between the two views as it is. In this case the view controller would implement the table view delegate methods and add the table view to the bottom view. Then add the map to the top view. This should allow you to catch the table view cell selection and update the map in the top view accordingly.
Example based on the structure I believe you are using:
public class FirstViewController {
public let parallaxView = MMParallaxView()
private var secondViewController: SecondViewController
private var thirdViewController: ThirdViewController
override open func viewDidLoad() {
secondViewController = SecondViewController()
thirdViewController = ThirdViewController()
super.viewDidLoad()
self.view.addSubview(parallaxView)
thirdViewController.delegate = secondViewController
parallaxView.parallaxTopView = secondViewController.view
self.addChildViewController(secondViewController)
secondViewController.didMove(toParentViewController: self)
parallaxView.parallaxBottomView = thirdViewController.view
self.addChildViewController(thirdViewController)
thirdViewController.didMove(toParentViewController: self)
}
}
public class SecondViewController: ThirdViewControllerDelegate {
public func updateMap() {
// Update map
}
}
public class ThirdViewController: UITableViewDelegate {
weak var delegate ThirdViewControllerDelegate?
private var table: UITableView = UITableView()
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.updateMap()
}
}
protocol ThirdViewControllerDelegate: class {
public func updateMap() {}
}
if the view controllers are instantiated through the Storyboard then you can try this:
public class MasterViewController {
var secondViewController: SecondViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.performSegue(withIdentifier: "MMParallaxTop", sender: nil)
self.performSegue(withIdentifier: "MMParallaxBottom", sender: nil)
}
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondVC = segue.destination as? SecondViewController {
secondViewController = secondVC
}
if let thirdVC = segue.destination as? ThirdViewController, let second = secondViewController {
thirdVC.delegate = second
}
}
}

How to pass textfield data from third viewcontroller to first viewcontroller

I want to send the text which is in textfield in ViewControllerC to another textfield which is in ViewControllerA
By using delegate am trying to pass the text from ViewControllerC to ViewControllerA.
i cant get the logic what to write here delegate?.userDidEnterInformation() in ViewControllerC
could any one help me regarding this
ViewControllerC
protocol DataEnteredInDestinationDelegate: class {
func userDidEnterInformation(info: String)
}
class DestinationSearchViewController: MirroringViewController {
var delegate: DataEnteredInDestinationDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell: UITableViewCell? = tableView.cellForRow(at: indexPath)
componetsTextField.text = cell?.textLabel?.text
delegate?.userDidEnterInformation()
self.navigationController?.popToRootViewController(animated: true)
}
}
ViewControllerA
class HomeViewController: MirroringViewController, DataEnteredInDestinationDelegate
{
func userDidEnterInformation(info: String){
locationView.destination.text = info
}
}
Firstly you have to always mark delegate as weak e.g.:
weak var delegate: DataEnteredInDestinationDelegate?
and then you need to connect delegate like this:
let vcA = ViewControllerA()
let vcC = ViewControllerC()
vcC.delegate = vcA // Connect delegate
and then your delegate method in ViewControllerC will work after invoking this code:
delegate?.userDidEnterInformation(textString)
Here NotificationCentre can be a good approach instead of delegates. Make Viewcontroller A an observer to receive text information as below.
Write this code in viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(userDidEnterInformation(notification:)), name: NSNotification.Name.init(rawValue: "UserDidEnterInformation"), object: nil)
and write this anywhere in class Viewcontroller A
func userDidEnterInformation(notification: Notification) {
if let textInfo = notification.userInfo?["textInfo"] {
textField.text = textInfo
}
}
In Viewcontroller C post the notification with textInfo by writing below code
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "UserDidEnterInformation"), object: nil, userInfo: ["textInfo": textField.text])
delegate?.userDidEnterInformation(cell!.textLabel!.text)
Also, you should set the delegate of ViewControllerC.
viewControllerC.delegate = viewControllerA
Consider the following example:-
let aVCobjA = UIViewController()
let aVCobjB = UIViewController()
let aVCobjC = UIViewController()
var aNavigation = UINavigationController()
func pushVC() {
aNavigation.pushViewController(aVCobjA, animated: true)
aNavigation.pushViewController(aVCobjB, animated: true)
aNavigation.pushViewController(aVCobjC, animated: true)
//Here you will get array of ViewControllers in stack of Navigationcontroller
print(aNavigation.viewControllers)
//To pass data from Viewcontroller C to ViewController A
self.passData()
}
// To pass data access stack of Navigation Controller as navigation controller provides a property viewControllers which gives you access of all view controllers that are pushed.
func passData() {
let aVCObj3 = aNavigation.viewControllers.last
let aVCObj1 = aNavigation.viewControllers[0]
//Now you have access to both view controller pass whatever data you want to pass
}

Trigger a function when user taps `Back` in detail view controller

I need to refresh my collectionView when user returns to that VC because what he/she did in the detailVC has affect on the previous VC data. I tried collectionView.reloadData() in both viewDidLoad() and viewDidAppear() of my VC has the collectionView in it. And It came up that when user taps the 'Back' in detailVC both viewDidLoad() and viewDidAppear() do not work. So, I tried to call one of them in detailVC with instantiate the firstVC(which has the collectionView)
then I got an runtime error which said collectionView is nil. Any thoughts? (BTW, the segue between them is ShowPush, and I can not change it because I have to have the transition of this segue in my app.)
Here is the firstVC:
class SkillsController: UIViewController{
#IBOutlet weak var collectionView: UICollectionView!
var TAGS: [TAG] = []
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "TagCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "tagCell")
self.sizingCell = (nib.instantiate(withOwner: nil, options: nil) as NSArray).firstObject as! TagCell?
self.loadMore()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("back to skills")
self.TAGS = TagManager.shared.tagList
collectionView.reloadData()
}
}
TAGS is my data which is stored in Realm database.
Here is the detailVC:
class SeeSelectedController: UICollectionViewController {
var TAGS: [TAG] = []
#IBOutlet weak var layout: FSQCollectionViewAlignedLayout!
override func viewDidLoad() {
super.viewDidLoad()
if currentTab.shared.isSkill {
self.title = "Selected Skills"
//init tags
let list = RealmManager.shared.skills
if let list = list {
for element in list {
TAGS.append(TAG(n: element.value!, iS: true))
}
}
collectionView?.reloadData()
}else{
self.title = "Selected Needs"
//init tags
let list = RealmManager.shared.needs
if let list = list {
for element in list {
TAGS.append(TAG(n: element.value!, iS: true))
}
}
collectionView?.reloadData()
}
let nib = UINib(nibName: "TagCell", bundle: nil)
collectionView?.register(nib, forCellWithReuseIdentifier: "tagCell")
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = TAGS[indexPath.row].name!
let currentState = TAGS[indexPath.row].isSelected!
TAGS[indexPath.row].isSelected = currentState ? false:true
if currentState {
print("deselect")
//remove from realm
RealmManager.shared.deleteItemFromList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}else{
print("select")
//add to realm
RealmManager.shared.addItemToList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}
if currentTab.shared.isSkill {
let VC: SkillsController = storyboard?.instantiateViewController(withIdentifier: "SkillsController") as! SkillsController
VC.viewDidAppear(true)
}
collectionView.reloadData()
//addd
}
}
So how it is working? in the SkillsVC user can select some tags from a pool, in the detailVC which is SeeSelecteVC he/she can drop selected tags. It is constantly changing in the Realm as you can see. The problem when user has dropped some tags in detailVC and press the Back button, the dropped tags are still looking as selected in SkillsVC. However when if user goes another VC and comes back to SkillsVC (by this way the viewDidLoad() is gonna work) the dropped tags are seems to be unselected. That's all.
If what you are looking for is just to reload on back button
What you can do is create your own custom UIBarButtonItem that will make you navigate backwards from your "detail view controller". What you should do next after adding your own back button is add an IBAction for UIBarBUttonItem and pop your "detail view controller".
Right before you do this, you should create a delegate that will be executed before the popping happens that will reload your UICollectionView.
The following is not the best way to achieve what you want:
In your didSelectItem for your second view controller, you are creating a new view controller here and you shouldn't force call viewDidAppear. Since you are creating a new UIViewController, you are not referencing the previous UIViewController that you came from and soo your UICollectionView is nil.
if currentTab.shared.isSkill {
//remove the below lines and call the delegate here
let VC: SkillsController = storyboard?.instantiateViewController(withIdentifier: "SkillsController") as! SkillsController
VC.viewDidAppear(true)
}
collectionView.reloadData()
What you should be doing is:
You should use delegates to send callbacks to previous view controllers or perform actions.
To create a delegate-
Using the first approach (using your own back button)-
protocol delegateVC{
func reloadCollectionView()
}
class SeeSelectedController: UICollectionViewController{
//add this inside this class
var delegate : delegateVC?
...
//implement your IBAction for back button and inside it-
... {
self.delegate.reloadCollectionView()
}
}
OR the second approach i pointed out (Just change your didSelectItem and it will reload the collectionView, no need to fret about back button at all and save the hassle, i strongly recommend this approach)
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = TAGS[indexPath.row].name!
let currentState = TAGS[indexPath.row].isSelected!
TAGS[indexPath.row].isSelected = currentState ? false:true
if currentState {
print("deselect")
//remove from realm
RealmManager.shared.deleteItemFromList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}else{
print("select")
//add to realm
RealmManager.shared.addItemToList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}
if currentTab.shared.isSkill {
self.delegate.reloadCollectionView()
}
}
}
And in your first view controller-
func reloadCollectionView(){
collectionView.reloadData()
}
Note: In your prepareForSegue remember to set the delegate of your detail view controller to be your first view controller

Swift 2 - Protocol Delegate between two UICollectionViewControllers

I am trying to create a protocol delegate between two UICollectionViewController. with the code I have I don't get any errors or warnings however, I cannot get the delegate to work. What am I missing?
Second Collection View
public protocol LettersCollectionViewDelegate: class {
func DidSelectLetter(collectioView: UICollectionView,letter: Character, resultString:String)
}
class LettersCollectionView: UICollectionViewController {
// DELEGATE
weak var delegate: LettersCollectionViewDelegate?
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)! as! LetterCellView
delegate?.DidSelectLetter(collectionView, letter: "T", resultString:"TestString")
}
}
First Collection View
class AnswerCollectionView: UICollectionViewController {
let lettersView = LettersCollectionView()
override func viewDidLoad() {
super.viewDidLoad()
self.lettersView.delegate = self
}
}
extension AnswerCollectionView: LettersCollectionViewDelegate {
func DidSelectLetter(collectioView: UICollectionView, letter: Character, resultString: String) {
print(letter)
}
}
UPDATE
You need your delegate to be the instance of the AnswerCollectionView that is embedded in your root view controller. Similarly, you need to set the delegate on the LettersCollectionView instance that is in the root view. let lettersView = LettersCollectionView() creates a new instance.
You can get the required references in prepareForSegue in your root view controller. You need to give the two embded segues in your storyboard identifiers, so you can identify them.
class ViewController: UIViewController {
var lettersView: LettersCollectionView?
var answersView: AnswersCollectionView?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "lettersSegue" {
let lettersView = segue.destinationViewController as? LettersCollectionView
} else if segue.identifier = "answersSegue" {
let answersView = segue.destinationViewController as? AnswersCollectionView
}
self.lettersView?.delegate = self.answersView
}

Resources