Pass data back from a popover view controller on iPhone - ios

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
}
}

Related

Segue and delegates in UIKit

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

Delegate between TableViewController and ViewController

I'm in my first week of developing in iOS and have become stuck on an issue with passing data between view controllers. My set up consists of a view with VC having a button in it and also a container view (no associated view controller). The container view has an embedded Segue to a TableView with TableViewController. The table has 6 rows and each row has a stepper that can change the value of a text view on the associated row. What I would like to do is collect the values of all the textviews when I press the button on the main view.
I am trying to use delegate to do this but when I press the button the returned value is always nil. I believe the problem is to do with the fact the VC is not being passed to the table view controller via the prepareForSegue function but I'm not sure why? Could be to do with the load order of the controllers?
import UIKit
class PredictionViewController: UIViewController, PredictionDelegate {
var predictionData: String!
#IBOutlet weak var messageTextBox: UITextView!
#IBOutlet weak var predictionSubmitButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.messageTextBox.isEditable = false
}
override func viewDidAppear(_ animated: Bool) {
}
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "predictionSegue") {
// pass data to next view
let vc = segue.destination as! PredictionsTableViewController
vc.predictionHomeDelegate = self
}
}
func receiveData(with data: String) {
predictionData = data
print(predictionData)
}
#IBAction func predictionSubmitButtonAction(_ sender: UIButton) {
print(predictionData)
}
}
TableViewController: (stripped to minimum)
import UIKit
protocol PredictionDelegate: class {
func receiveData(with data: String)
}
class PredictionsTableViewController: UITableViewController, PredictionDelegate {
weak var predictionHomeDelegate: PredictionDelegate?
#IBOutlet weak var homeTeamScore1: UITextView!
#IBOutlet weak var homeTeamStepper1: UIStepper!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
}
func getPredictionList() {
//does some stuff
self.passDataBackwards()
}
func receiveData(with data: String) {}
func passDataBackwards() {
let data = "{\"score\":\"1\"}"
predictionHomeDelegate?.receiveData(with: data)
}
#IBAction func homeTeamStepper1Action(_ sender: UIStepper) {
let score = Int(sender.value).description
homeTeamScore1.text = score
self.passDataBackwards()
}
}
Any help gratefully received!
Edit:
After comments...
You have the wrong idea about Protocols and Delegates. They are not needed here.
Instead, in your "home" VC, get a reference to the embedded VC. Then, add a function in your embedded VC that you can call to get its data.
// "home" view controller
class PredictionViewController: UIViewController {
// this will be a reference to the embedded view controller
var embeddedVC: PredictionsTableViewController?
#IBAction func getDataButtonTapped(_ sender: Any) {
if let tableData = embeddedVC?.getMyData() {
print("Result: \(tableData)")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "predictionSegue") {
if let vc = segue.destination as? PredictionsTableViewController {
// get a reference to the embedded VC
self.embeddedVC = vc
}
}
}
}
// embedded view controller
class PredictionsTableViewController: UIViewController {
var numTaps = 0
func getMyData() -> String {
return "\(numTaps)"
}
#IBAction func didTap(_ sender: Any) {
numTaps += 1
}
}
You're close, but a couple mistakes...
Here is a very, very simple example. View controller with container, which has a view controller with a button.
Code:
import UIKit
// your protocol
protocol PredictionDelegate: class {
func receiveData(with data: String)
}
// embedded view controller
// NOTE: this should NOT include PredictionDelegate
class PredictionsTableViewController: UIViewController {
weak var predictionHomeDelegate: PredictionDelegate?
#IBAction func didTap(_ sender: Any) {
// on button tap, "send data back"
predictionHomeDelegate?.receiveData(with: "Test")
}
}
// "home" view controller
// NOTE: this DOES include PredictionDelegate
class PredictionViewController: UIViewController, PredictionDelegate {
func receiveData(with data: String) {
print(data)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "predictionSegue") {
if let vc = segue.destination as? PredictionsTableViewController {
// set self as the delegate of the embedded PredictionsTableViewController
vc.predictionHomeDelegate = self
}
}
}
}
Notes:
Do NOT include func receiveData(with data: String) {} in your embedded view controller
Do NOT assign PredictionDelegate to your embedded view controller
You need to hook your segue to the vc itself instead of the cell and use
self.performSegue(withIdentifier: "predictionSegue", sender: nil)
Since these are two separate screens I am not too sure it makes too much sense to have the button on the first view controller that submits data from the second, can the button not just be on the second. If for whatever reason it can't you could pass the data back to the first view controller on segueing back by adding a public variable to the first view controller and adding another prepare method to the second to pass the data back like you have done when adding the delegate.

Calling child view controller using segmented control action?

I have two child view controllers in my parent view controller, I want to call them upon a value change in the segmented control, and want to set the value of the parent imageView through child view controllers.
protocol UserEdittedPhoto {
func UserIsDone(image:UIImage)
}
class ControllerFinal:UIViewController, UserEdittedPhoto{
func UserIsDone(imageEditted: UIImage){
self.usedImage=imageEditted
self.imageView.image=self.usedImage
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentAction(sender:UISegmentedControl){
if (segmentedControl.selectedSegmentIndex==0){
performSegueWithIdentifier("EditIAm", sender: nil)
}
else if (segmentedControl.selectedSegmentIndex==1){
performSegueWithIdentifier("EditIAm", sender: nil)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditIAm"{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("ControllerEdit")
self.presentViewController(controller, animated: true, completion: nil)
let nextView = segue.destinationViewController as! ControllerEdit
nextView.originalImage=self.imageView.image!
nextView.delegate=self
}
else if segue.identifier == "FilterIAm"{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("ControllerFilters")
self.presentViewController(controller, animated: true, completion: nil)
let nextView = segue.destinationViewController as! ControllerFilters
nextView.toBeFilter=self.imageView.image!
}
}
class ControllerEdit:UIViewController{
var delegate: UserEdittedPhoto? = nil
if (delegate != nil){
self.originalImage = UIImage(CIImage: CIImage(image: self.originalImage)!.exposureAdjustFilter(sliderValue.value)!)
self.delegate!.UserIsDone(self.originalImage)
print("I am Called!")
}
}
class ControllerFilters:UIViewController{
override func viewDidLoad() {
print("SAHASHhqdwiuhiuhsaiuhsaiudhiuash")
controllerFinal?.imageView.image=toBeFilter
print("SAHASHhqdwiuhiuhsaiuhsaiudhiuash")
super.viewDidLoad()
}
}
UPDATE
To reflect our discussion in the comments below, I don't think you really need Containers and View Controllers to manage your custom controls (Edit / Filters). It's overkill.
Instead, I think you should be creating custom Views, and then adding them to your main Storyboard.
Then you could simply hide/show your custom Views when users tap on the Segmented Control as well as passing values to them, for example:
CustomEditView.valueY = newValueY
CustomFiltersView.valueX = newValueX
Regarding:
I need to call it forcefully through segmentedControl action, so that
my values in the childView be updated
Then you need to map the target View Controllers to local variables and use them to update the target View Controller variables when users presses the segments.
I've update the code and "demo" in my answer to reflect that.
(Notice that I'm just putting random Strings in the labels to make a point.)
Now to the complete answer...
In the setup you described in your other question, which is based on containers, the View Controllers are already there, in the Storyboard. You absolutely don't need to present them again (you can remove performSegueWithIdentifier calls).
If I understood correctly, you just want to show different "controllers" to the user based on what they choose via a Segmented Control.
There are some ways for doing that, but the easiest one would be to hide and to show the containers of the ControllerEdit / ControllerFilters View Controllers -- by changing the containers isHidden variable state.
Like this:
Storyboard setup:
Code (based on my other answer):
import UIKit
protocol UpdateImageProtocol {
func userIsDone(image: UIImage)
}
class ViewController: UIViewController, UpdateImageProtocol {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var changeImageContainer: UIView!
var controllerEdit: ControllerEdit?
#IBOutlet weak var applyFilterContainer: UIView!
var controllerFilters: ControllerFilters?
var image = UIImage(named: "hello")
override func viewDidLoad() {
super.viewDidLoad()
userIsDone(image: image!)
}
func userIsDone(image: UIImage) {
imageView.image = image
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "controllerEdit" {
let nextView = segue.destination as! ControllerEdit
nextView.delegate = self
controllerEdit = nextView
} else if segue.identifier == "controllerFilters" {
let nextView = segue.destination as! ControllerFilters
controllerFilters = nextView
}
}
#IBAction func segmentAction(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
changeImageContainer.isHidden = false
applyFilterContainer.isHidden = true
controllerEdit?.customLabel.text = String(arc4random_uniform(999))
} else {
changeImageContainer.isHidden = true
applyFilterContainer.isHidden = false
controllerFilters?.customLabel.text = String(arc4random_uniform(999))
}
}
}
class ControllerEdit: UIViewController {
#IBOutlet weak var customLabel: UILabel!
var image = UIImage(named: "newHello")
var delegate: UpdateImageProtocol?
#IBAction func changeImage(sender: UIButton) {
delegate?.userIsDone(image: image!)
}
}
class ControllerFilters: UIViewController {
#IBOutlet weak var customLabel: UILabel!
// TODO
}
Put a breakpoint in this function:
#IBAction func segmentAction(sender:UISegmentedControl){
if (segmentedControl.selectedSegmentIndex==0){
performSegueWithIdentifier("EditIAm", sender: nil)
}
else if (segmentedControl.selectedSegmentIndex==1){
performSegueWithIdentifier("EditIAm", sender: nil)
}
}
If it's not getting called, then you probably didn't connect it to an action in IB (is the circle to the left of the the #IBAction filled in?)
If it is getting called, then make sure the segue names are right -- also, fix the one in the else if, because it looks like you want "FilterIAm" there.
Then, put a breakpoint in prepareForSegue:... -- is that getting called? If not, recheck the names are the same as in IB.
EDIT: based on comment
Your prepareForSegue is not supposed to create the ViewController. The destination view controller is created as a consequence of performing the segue and passed to this function.
if segue.identifier == "EditIAm"{
let nextView = segue.destinationViewController as! ControllerEdit
nextView.originalImage=self.imageView.image!
nextView.delegate=self
}
You don't need to present anything -- the destinationViewController is going to be presented. You can set any of its variables (as you have) - that is what is meant by preparing for the segue.

how to send data from popover view controller to main view controller. Pass a string so that i can make that as the label text in the main view

I tried many methods to send data from my popup view controller to main view controller. but failed. can anybody help me with this.
i am using a "present as popover" segue. i want to the text entered in textfield of popover view as the label text of main view.
From Popup View, Data send to Main ViewController using protocol in Swift 3.
enter image description here
Complete Details are given below...
1. View Controller Implementing with Protocol named sendDataToViewProtocol.
import UIKit
class ViewController: UIViewController,sendDataToViewProtocol {
#IBOutlet weak var lshowDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnShowPopUpDialog(_ sender: Any) {
let popUpVc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PopupVIewController") as! PopupVIewController
//Don't forget initialize protocal deletage
popUpVc.delegate = self
self.addChildViewController(popUpVc)
popUpVc.view.frame = self.view.frame
self.view.addSubview(popUpVc.view)
popUpVc.didMove(toParentViewController: self)
}
func inputData(data: String) {
lshowDataLabel.text = data
}
}
Popup View Controller With Protocol named sendDataToViewProtocol below.
3.protocol declare outside the PopupVIewController.
Don't forget to assign ViewController to PopupVIewController .
In viewController withIdentifier: "PopupVIewController" , "PopupVIewController" is PopupVIewController storyborad Id.
Please see the attached image.
import UIKit
protocol sendDataToViewProtocol {
func inputData(data:String)
}
class PopupVIewController: UIViewController {
//Protocol object
var delegate:sendDataToViewProtocol? = nil
#IBOutlet weak var txtInputFieldText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor
.black.withAlphaComponent(0.8)
}
#IBAction func btnSendDataToViewController(_ sender: Any) {
//"Check Delegate nil"
if(delegate != nil){
//Check textField is empty
if(txtInputFieldText.text != ""){
//set textField Data to protocol Function
delegate?.inputData(data: txtInputFieldText.text!)
self.view.removeFromSuperview()
}
}
}
#IBAction func btnClose(_ sender: Any) {
self.view.removeFromSuperview()
}
}
First of all, keep a temporary variable in your Main ViewController. Let's call it:
var somethingCool: String?
Then, in your popup ViewController code, assuming you have your segue trigger there, you will need to add in a new method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "your_segue_identifier" {
if let vc = segue.destination as? MainViewController {
vc.somethingCool = "whatever_you_want"
}
}
}

Segue not running after MPC viewcontroller is dismissed

I have an action tied to pop up the MPC view controller however once this action is complected and the browser is dismissed I simply am lead back to my old view controller
func browserViewControllerDidFinish(
browserViewController: MCBrowserViewController!) {
// Called when the browser view controller is dismissed (ie the Done
// button was tapped)
func seguetoJoin(segue: UIStoryboardSegue, sender: AnyObject?) {
var joinView: playMusicViewController = segue.destinationViewController as! playMusicViewController
weak var delegate: MCBrowserViewControllerDelegate!
}
self.dismissViewControllerAnimated(true, completion: nil)
}
You seem to be calling dismissViewController on the VC that is receiving the didFinish callback.
You should move the segue code to the ViewController that needs to perform the segue, rather than performing it on a ViewController that is being dismissed.
Also, these are unused
var joinView: playMusicViewController = segue.destinationViewController as! playMusicViewController
weak var delegate: MCBrowserViewControllerDelegate!
Update -
Following the comments below.
in mpcSubClass.m
var viewControllerToPeformSegue: UIViewController!
func browserViewControllerDidFinish(browserViewController: MCBrowserViewController!) {
// animation:false as your segue will have an animation
self.dismissViewControllerAnimated(false, completion: nil)
self.viewControllerToPerformSegue.handleBrowserDidFinish()
}
in viewControllerToPerformSegue.m
// when you initialise the MPC VC subclass
mpcVCSubclass.viewControllerToPerformSegue = self
func handleBrowserDidFinish()
{
self.performSegueWithIdentifier("mainViewSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
{
if segue.identifier == "mainViewSegue"
{
let mainVC = segue.destination as! MainVC
// now pass any data to your main VC that it can load in viewWillAppear
}
}
in storyboard
link viewControllerToPerformSegue to the MainVC and name the segue mainViewSegue
Hope this helps.

Resources