I'm using swift4, i present view controller modally as popover and want to pass data back to previous view controller when dismissing current popover, this is the first View controller :
import UIKit
class SearchResultViewController: UIViewController, UIPopoverPresentationControllerDelegate, PopoupDelegate {
#IBOutlet var errorLable: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "pop") {
let dest = segue.destination
if let pop = dest.popoverPresentationController {
dest.preferredContentSize = CGSize(width: self.view.frame.size.height - 20, height: 500)
pop.delegate = self
}
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
}
and this the popover code:
import UIKit
class SearchPopoverViewController: UIViewController {
#IBOutlet var cityLable: UILabel!
var delegate: PopoupDelegate?
#IBAction func closeButtonAction(_ sender: Any) {
self.delegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
}
and this is the PopoupDelegate
import Foundation
protocol PopoupDelegate {
func popupValueSelected(value: String)
}
when i click in the popover close button, the popover should dismiss and the errorLable in SearchResultViewController should change it's text to new passed text, but none happens, only the popover dismiss,
what should i do?
You delegate function is wrong, function inside function in SearchResultViewController:
func popupValueSelected(value: String) {
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
}
It should be:
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
After editing the question :
You never assigned any instance to the delegate variable. Try to change delegate variable name to popoUpDelegate.
pop.delegate is for UIPopoverPresentationControllerDelegate not your delegate. This function should be like this :
if (segue.identifier == "pop") {
if let dest = segue.destination.popoverPresentationController as? SearchPopoverViewController {
dest.preferredContentSize = CGSize(width: self.view.frame.size.height - 20, height: 500)
dest.popoUpDelegate = self
}
}
}
And your SearchPopoverViewController look like this :
class SearchPopoverViewController: UIViewController {
#IBOutlet var cityLable: UILabel!
var popoUpDelegate: PopoupDelegate?
#IBAction func closeButtonAction(_ sender: Any) {
self.popoUpDelegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
}
you have to define delegate to SearchResultViewController.
var PopoupDelegate : PopoupDelegate?
and before pop viewcontroller assign delegate to popoupDelegate
PopoupDelegate.delegate = self
and you have right function inside function. first make it correct.
Try this. Hope this will work. and let me know in case of any concern
When you are presenting SearchPopoverViewController from SearchResultViewController you need to set delegate.
Using segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueToPopOver") {
let dest = segue.destination
if let vc = dest.popoverPresentationController as? SearchPopoverViewController {
//set other properties
vc.delegate = self
}
}
}
Using UINavigationController
let vc = SearchPopoverViewController() // initialise view controller
vc.delegate = self
present(vc, animated: true, completion: nil)
Respond to delegate
#IBAction func closeButtonAction(_ sender: Any) {
self.delegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
In SearchResultViewController implement delegate method
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
I think you have a fundamental misunderstanding as to how segues and protocols work for view controllers. Frankly, I would recommended reviewing the topic in Apple's App Development with Swift. This is written clearly with exercises to help you understand the basics of passing information between view controllers. I have been where you are, and while the suggestions and comments have been correct, you don't seem to have the foundation to understand what they are saying. Good luck!
I used popover dismiss delegate instead of protocol:
this code solved my problem:
in the SearchResultViewController:
func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
if let searchPopoverViewController =
popoverPresentationController.presentedViewController as? SearchPopoverViewController{
self.passed_city_id = searchPopoverViewController.selected_city_id
}
}
in the SearchPopoverViewController
#IBAction func searchButtonAction(_ sender: Any) {
self.selected_city_id = 10
popoverPresentationController?.delegate?.popoverPresentationControllerDidDismissPopover?(popoverPresentationController!)
self.dismiss(animated: true, completion: nil)
}
you can use "Unwind segue". pls refer
https://medium.com/#mimicatcodes/create-unwind-segues-in-swift-3-8793f7d23c6f
Related
I have 2 Controllers: MainVC and SideMenuVC.
I wanted to modify MainVC using SideMenuVC, so created delegate of SideMenuVC ( as well, there's Emdebed segue "name..." to "Side Menu View Controller" on storyboard because MainViewController has subView, which contains ContainerView - this container is our SideMenuVC. And this delegate works as he should.
However, due to logic in the app, I also need to send data from MAINVC to SIDEMENUVC.
So i did the same - created another delegate of second VC... But I turned out, MainViewControllerDelegate is not responding in SideMenuViewController. And i'm absolutely clueless...
Yes, i do implement necessary protocols in both classes, in extension!
Code of both VCs below, screens of storyboard in the attachment
MainViewController + MainViewControllerDelegate
protocol MainViewControllerDelegate{
func isImageLoaded(_ isLoaded:Bool)
}
class MainViewController: UIViewController {
/* ... */
var delegate: MainViewControllerDelegate?
var sideMenuViewController: SideMenuViewController?
private var isSideMenuPresented:Bool = false
private var isImageLoaded:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
self.isImageLoaded = false
self.setupUI()
self.delegate?.isImageLoaded(false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "MainVC_SideMenuVC_Segue")
{
if let controller = segue.destination as? SideMenuViewController
{
self.sideMenuViewController = controller
self.sideMenuViewController?.delegate = self
}
}
}
/* ... */
//I'm using PHPicker, and when new image is selected, i want to send "true" via delegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
if let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self){
let previousImage = self.presentedImage.image
itemProvider.loadObject(ofClass: UIImage.self){ [weak self] image, error in
DispatchQueue.main.async {
guard let self = self, let image = image as? UIImage, self.presentedImage.image == previousImage else {
return
}
self.presentedImage.image = image
self.isImageLoaded = true;
self.delegate?.isImageLoaded(true)
}
}
}
}
SideMenuViewController + SideMenuViewControllerDelegate
protocol SideMenuViewControllerDelegate{
func hideSideMenu()
func performAction(_ type:OperationType)
}
class SideMenuViewController: UIViewController {
/*...*/
var delegate: SideMenuViewControllerDelegate?
var mainViewController: MainViewController?
private var menuData: [ExpandingCellModel] = []
private var isImageLoaded: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// self.mainViewController?.delegate = self
menuData = setupData()
setupUI()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? MainViewController {
controller.delegate = self
}
}
}
/* ... */
Here is what I think is happening.
There is segue happening from MainVC to SideMenuVC but there is no segue actually happening between SideMenuVC to MainVC in my opinion.
Happening is keyword because there is an EmbedSegue from MainVC to SideMenuVC but where is the segue from SideMenuVC to MainVC ? You did some connection in storyboard but nothing is happening in my opinion.
That is why in override func prepare is being called as planned in MainViewControllerDelegate and the delegate is getting set but it is not getting set in SideMenuViewController since override func prepare doesn't get called as no segue happens.
What you can do instead which might work is set both the delegates inside prepare in MainViewControllerDelegate
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "MainVC_SideMenuVC_Segue")
{
if let sideMenuVC = segue.destination as? SideMenuViewController
{
sideMenuVC.delegate = self
// assign MainViewControllerDelegate here
self.delegate = sideMenuVC
}
}
}
Check now if data is sent back to main view controller also.
If your issue is still not yet solved, please have a look and try this small example I set for you in github to show passing data between mainVC and embeddedVC and it might give you some hints.
You can see the result of this in action here: https://youtu.be/J7C7SEC04_E
i am new to iOS here is my question:
I have a saveCardViewController (Presented Modally) with some textFields and Save button.
#IBAction func Save(_ sender: UIButton) {
date = datePicker.date
try! realm.write() {
sessionCard.pokerType = pokerTypeSegment.titleForSegment(at: pokerTypeSegment.selectedSegmentIndex)!
date = dateFormatter.string(from: date)
sessionCard.handsPlayed = Int(handPlayedTextlabel.text!) ?? 0
sessionCard.moneyIn = Int(moneyInTextLabel.text!) ?? 0
sessionCard.moneyOut = Int(moneyOutTextLabel.text!) ?? 0
sessionCard.timePlayed = Int(timePlayedTextLabel.text!) ?? 0
sessionCard.sortDate = date
realm.add(sessionCard)
}
dismiss(animated: true, completion: nil)
}
How can I reloadData() on my main ViewController, after Save button is pressed and saveCardViewController is dismissed.
Thanks!
EDIT # 1:
Thank you #davidev for your answer,I made changes but still does not update
My ViewController With TableView:
class SessionViewController: BackgroundViewController, RefreshViewDelegate {
func refreshView() {
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
My ViewController with data and Save button:
protocol RefreshViewDelegate {
func refreshView()
}
class AddSessionViewController: UIViewController, UITextFieldDelegate {
var delegate: RefreshViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func TEST2(_ sender: UIButton) {
delegate?.refreshView()
dismiss(animated: true, completion: nil)
}
You can use delegate pattern to achieve this.
Declare your Refresh Protocol like this:
protocol RefreshViewDelegate {
func refreshView()
}
Make your parent view conform to this protocol and implement refreshView() with your custom refresh action. Also make sure to set the delegate of the child view to self.
Inside saveCardViewController declare your delegate variable
var delegate : RefreshViewDelegate?
And call the delegate action inside your IBaction
delegate?.refreshView()
Edit:
I just saw your updated code. As you are using Storyboard segues, you still have to set the delegate via code. In your main view controller add the function:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if let viewController = segue.destinationViewController as? AddSessionViewController
{
viewController.delegate = self
}
}
I made a drawer with two sections.. therefore I implemented a delegate pattern to access a embedded tableView inside of my container.
class Drawer: UIViewController, DrawerTableViewDelegate {
var drawerVC : DrawerTableView?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "tableViewSegue" {
self.drawerVC = (segue.destination as! DrawerTableView
self.drawerVC!.delegate = self
}
}
In my DrawerTableView class is the delegate implemented and also some code to access the rows in the tableView:
class MenuTableViewController: UITableViewController {
var delegate : MenuTableViewControllerDelegate?
//tableview delegate
The drawer gets called in two scenes in the same way:
#objc func didTapRowInDrawer(_ sender: UIBarButtonItem) {
guard let drawerVC = storyboard?.instantiateViewController(withIdentifier: "DrawerCtrl") as? Drawer else { return }
drawerVC.modalPresentationStyle = .overCurrentContext
drawerVC.transitioningDelegate = self
present(drawerVC, animated: true)
}
Can I determine the calling ViewController of my drawer?
I thought of something like
if segue.source is VC {
//do something
} else if segue.source is VC2 {
// do something else
}
in Drawer::prepare
From the code, it looks like the Drawer was presented as a Modal. The viewController which presented a Modal is stored in presentingViewController variable. You can use the below code in the DrawerViewController to determine which ViewController had presented the Drawer.
if let presentingVC = self.presentingViewController {
if presentingVC.isKind(of: VC1.self) {
//do something
} else if presentingVC.isKind(of: VC2.self) {
// do something else
}
}
There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?
You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC
While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.
Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}
How can I pass data back from a PopoverViewController to the main view controller on an iPhone?
I know I'm doing something terribly wrong but I cannot figure it out.
Here is the code:
PopoverViewController.swift
protocol PopoverViewControllerDelegate {
func messageData(data: AnyObject)
}
class PopoverViewController: UIViewController {
#IBOutlet weak var inputMessage: UITextField!
var delegate: PopoverViewControllerDelegate?
#IBAction func sendData(sender: AnyObject) {
if inputMessage.text != ""{
self.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
self.delegate?.messageData(inputMessage.text!)
}
}
}
Main ViewController.swift:
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate, PopoverViewControllerDelegate {
#IBOutlet weak var showData: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// popover segue
if segue.identifier == "popoverSegue" {
let popoverViewController = segue.destinationViewController
popoverViewController.popoverPresentationController!.delegate = self
}
// code to comunicate with data in popoverViewController
let pvc = storyboard?.instantiateViewControllerWithIdentifier("popoverViewController") as! PopoverViewController
pvc.delegate = self
self.presentViewController(pvc, animated:false, completion:nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
func messageData(data: AnyObject) {
self.showData.text = "\(data)"
}
}
With the code above I can pass data back to the main view controller without a problem, the issue is that the popover doesn't work, it just acts like a regular ViewController occupying the whole screen.
The funny thing is that if I comment the following line of code the popover works but I can no longer pass data back, I can see the popover but the passing data stops working.
// if I comment this line
self.presentViewController(pvc, animated:false, completion:nil)
I don't get any errors, one just stops working.
Any suggestions?
Thanks a lot
In prepareForSegue, the destinationViewController is your PopoverViewController. You need to cast it to that and set the delegate on that so that you can pass back data, and you need to set the popoverPesentationController?.delegate as well. You don't need the rest of the code in prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// popover segue
if segue.identifier == "popoverSegue" {
let popoverViewController = segue.destinationViewController as! PopoverViewController
popoverViewController.delegate = self
popoverViewController.popoverPresentationController?.delegate = self
}
}