Passing Data between ViewController in swift4 - ios

In this above Picture Two views are there. Top one is a view for showing date(for this view the class name is calender view) . And the bottom one is a tableview.the current view name is Main View When i am clicking on the cell of the tableview then it will go to the next view. When i am dismising the next view i want to pass some data to the calender view.How to achive this.
class MainView: UIViewController, UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let next = self.storyboard?.instantiateViewController(withIdentifier: "NextVC") as! NextVC
self.present(next, animated: true, completion: nil)
}
}
class NextVC: UIViewController {
var sendingData: String?
#IBAction func backAction(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
class CalenderView: UIViewController{
var receiveValue: String?
}
Here i want when i am dismissing the nextview the value of sendingData will pass to calender view.
How to do this ? Please help.

I can give you two solutions:
You can create your on custom notification.
First of all on back action you should post your notification with sendingData.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: UpdateCalendarNotification), object: sendingData)
Next, on main view controller with calendar view you should register notification for "UpdateCalendarNotification" name.
func registerNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(updateCalendarView(_:)),
name: NSNotification.Name(rawValue: "UpdateCalendarNotification"),
object: nil)
}
And on selector updateCalendarView(_:) you should handle changes for calendar view.
#objc
func updateCalendarView(_ notification: NSNotification) {
if let sendingData = notification.object as? String {
/// Update calendar view
}
}
Second solution is public block for you "next controller".
On next view controller you should add this handler:
var onDismiss: ((String?) -> Void)?
and in backAction method you should pass your data
onDismiss?(sendingData)
In your main view controller you should implement this block like this:
let next = self.storyboard?.instantiateViewController(withIdentifier: "NextVC") as! NextVC
next.onDismiss = { [weak self] (sendingData) in
self?.calendarView.receiveValue = sendingData
}
self.present(next, animated: true, completion: nil)
I hope this will help you)

Related

Swift: Protocol and Delegates passing data to previous view controller

I am working in Swift and the function categoryPressedFunction is not being called.
Protocol:
protocol categoryPressed: class { //1 create a protocol with function that passes a string
func categoryPressedFunction(category: String)
}
View Controller 2:
//set the delegate
weak var delegate: categoryPressed?
//when cell is selected in tableview, grab the "category" which is a string and then dismiss
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let category = guideCategoryArray[indexPath.row] // gets category
delegate?.categoryPressedFunction(category: category) // sets delegate to know it was pressed
self.presentingViewController?.dismiss(animated: true, completion: nil) //dismisses
}
}
View Controller 1 (Previous)
//set class delegate
...categoryPressed
//add function that sets label to category and looks up items based off category
func categoryPressedFunction(category: String) {
print("categoryPressedFunctionPressed")
resourceArray.removeAll()
resourceLabel.text = category
getItems(item: category, { [self] in
print("got new items for \(category) and refreshed the tableview")
self.resourceTableView.reloadData()
})
}
When returning to ViewController 1, nothing happens. The tableview does not reload, nor does the label change to the category pressed. Am I missing something?
Delegates might be nil. Did you add this line in the ViewDidLoad method?
delegate = self
You might have missed assigning delegate to self while moving from VC1 to VC2.
Hope below code helps you.
//Some Navigation logic
let VC2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VC2Identifier") as! VC2;
VC2.delegate = self
self.present(VC2, animated: true, completion: nil)

How to push a view from inside a UITableViewCell that is in a UICollectionViewCell?

I have a UITableView inside of a UICollectionViewCell and I'm trying to push a new view from the UITableViewCell inside of the tableView(_:didSelectRowAt:) method. However, I cannot access navigationController. How would I go about navigating to the new view?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var item = Item(name: "")
switch indexPath.section {
case 0:
item = array1![indexPath.item]
case 1:
item = array2![indexPath.item]
default:
break
}
let layout = UICollectionViewFlowLayout()
let newView = NewCollectionView(collectionViewLayout: layout)
newView.itemOfInterest = item
// Can't reference navigationController
}
Get parent controller of current View -
//MARK: get parent controller...
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
Usage:-
Get navigationController reference as parentViewController?.navigationController
let viewController = GymLearnMoreViewController(nibName: "GymLearnMoreViewController", bundle: nil) //Your View controller instance
parentViewController?.navigationController?.pushViewController(viewController, animated: true)
Source Apple:-
https://developer.apple.com/documentation/uikit/uiresponder/1621099-next
https://developer.apple.com/documentation/uikit/uiresponder?changes=_9
for this
1 . You need to declare a protocol in the collectionviewcell where the tableview is added
2 . implement the protocol method on the class where the collectionview is added. and handle the navigation on click from here
3 . set the collectionview cell delegate when the collectionviewCellForrow at indexpath method is invoked and call the delegate method when the tableview item is clicked within the collectionview cell
One way is using NotificationCenter which you can write in ParentViewController :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(methodNavigate(notification:)), name: NSNotification.Name(rawValue: "NavigateToSecondView"), object: nil)
}
#objc func methodNavigate(notification: NSNotification) {
Write your push segue code here.
}
Don't forget to remove observer in viewDidDisappear:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "NavigateToSecondView"), object: nil)
}
You can pass data through this too.
Then in didSelectRowAt :
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NavigateToSecondView"), object: "Send Data Here")
But I will suggest using protocol is much better and elegant way.
You can get lots of tutorials and samples on internet:
https://medium.com/#abhimuralidharan/all-about-protocols-in-swift-11a72d6ea354
https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
Hope it helps. Happy coding.

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
}

Passing data backwards by clicking a UITableView cell - iOS/Swift

My application has 3 UIViewControllers. ViewController #1-->#2 is by "show segue" and from #2-->#3 is by "present modally". #3 has a UITableView. When a cell is clicked, I would like to jump back to #1 and pass data from #3 as well.
I managed to dismiss #3 and go back to #2 by using this function, but this is not what I need. Please advise.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
self.dismiss(animated: true, completion: {
self.funcToCall(id: id)
}
you have to use delegate to pass the value to vc#1 and pop to root view controller.
You should be able to achieve the desired result by defining another segue that triggers on tapping the UITableViewCell. Make sure to assign it an identifier. Add the code below to the UITableView controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Segue Identifier Here" {
var dest = segue.destinationViewController as! DestViewController
dest.dataObj = dataToBePassed
}
Try pass data through NSNotificationCenter
Register to receive notification in VC1's ViewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(self.myData(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
// handle notification
func myData(_ notification: NSNotification) {
let object = notification.object // You data
}
Post this notification in VC3
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: **yourData**)
// Do popToRootViewContoller
You can use closures, here is an example:
ViewController3
class ViewController3: UIViewController {
var cellSelectedHandler: ((String)->Void)?
.
.
.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if cellSelectedHandler != nil {
cellSelectedHandler!("data you want to pass")
}
}
ViewController2
class ViewController2: UIViewController {
//add vc3 as instance variable
let vc3 = ViewController3()
ViewController1
class ViewController: UIViewController {
func presentViewController2() {
let vc2 = ViewController2()
vc2.vc3.cellSelectedHandler = {(dataPassed) in
vc2.vc3.dismiss(animated: true, completion: nil)
_ = vc2.navigationController?.popViewController(animated: true)
//use dataPassed
}
}

presentViewController from TableViewCell

I have a TableViewController, TableViewCell and a ViewController. I have a button in the TableViewCell and I want to present ViewController with presentViewController (but ViewController doesn't have a view on storyboard). I tried using:
#IBAction func playVideo(sender: AnyObject) {
let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)
}
Error: Value of type TableViewCell has no member presentViewController
Then, I tried
self.window?.rootViewController!.presentViewController(vc, animated: true, completion: nil)
Error: Warning: Attempt to present whose view is not in the window hierarchy!
What am I doing wrong? What should I do in order to presentViewController from TableViewCell? Also how can I pass data to the new presenting VC from TableViewCell?
Update:
protocol TableViewCellDelegate
{
buttonDidClicked(result: Int)
}
class TableViewCell: UITableViewCell {
#IBAction func play(sender: AnyObject) {
if let id = self.item?["id"].int {
self.delegate?.buttonDidClicked(id)
}
}
}
----------------------------------------
// in TableViewController
var delegate: TableViewCellDelegate?
func buttonDidClicked(result: Int) {
let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)
}
I receive error: Presenting view controllers on detached view controllers is discouraged
(Please note that I have a chain of NavBar & TabBar behind TableView.)
I also tried
self.parentViewController!.presentViewController(vc, animated: true, completion: nil)
Same Error.
Also tried,
self.view.window?.rootViewController?.presentViewController(vc, animated: true, completion: nil)
Same Error
It seems like you've already got the idea that to present a view controller, you need a view controller. So here's what you'll need to do:
Create a protocol that will notify the cell's controller that the button was pressed.
Create a property in your cell that holds a reference to the delegate that implements your protocol.
Call the protocol method on your delegate inside of the button action.
Implement the protocol method in your view controller.
When configuring your cell, pass the view controller to the cell as the delegate.
Here's some code:
// 1.
protocol PlayVideoCellProtocol {
func playVideoButtonDidSelect()
}
class TableViewCell {
// ...
// 2.
var delegate: PlayVideoCellProtocol!
// 3.
#IBAction func playVideo(sender: AnyObject) {
self.delegate.playVideoButtonDidSelect()
}
// ...
}
class TableViewController: SuperClass, PlayVideoCellProtocol {
// ...
// 4.
func playVideoButtonDidSelect() {
let viewController = ViewController() // Or however you want to create it.
self.presentViewController(viewController, animated: true, completion: nil)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath: NSIndexPath) -> UITableViewCell {
//... Your cell configuration
// 5.
cell.delegate = self
//...
}
//...
}
You should use protocol to pass the action back to tableViewController
1) Create a protocol in your cell class
2) Make the button action call your protocol func
3) Link your cell's protocol in tableViewController by cell.delegate = self
4) Implement the cell's protocol and add your code there
let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)
I had the same problem and found this code somewhere on stackoverflow, but I can't remember where so it's not my code but i'll present it here.
This is what I use when I want to display a view controller from anywhere, it gives some notice that keyWindow is disabled but it works fine.
extension UIApplication
{
class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController?
{
if let nav = base as? UINavigationController
{
let top = topViewController(nav.visibleViewController)
return top
}
if let tab = base as? UITabBarController
{
if let selected = tab.selectedViewController
{
let top = topViewController(selected)
return top
}
}
if let presented = base?.presentedViewController
{
let top = topViewController(presented)
return top
}
return base
}
}
And you can use it anywhere, in my case I used:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "WeekViewController")
UIApplication.topViewController()?.navigationController?.show(vc, sender: nil)
}
So self.presentViewController is a method from the ViewController.
The reason you are getting this error is because the "self" you are referring is the tableViewCell. And tableViewCell doesn't have method of presentViewController.
I think there are some options you can use:
1.add a delegate and protocol in the cell, when you click on the button, the IBAction will call
self.delegate?.didClickButton()
Then in your tableVC, you just need to implement this method and call self.presentViewController
2.use a storyboard and a segue
In the storyboard, drag from your button to the VC you want to go.

Resources