Passing data from Pop-over ViewController UITextfield back to Master ViewController - ios

I have an app setup to have a Master root viewController with a Navigation bar that has a "Settings button". When the user taps the settings button, it brings up a 'settingsTableViewController' that is not dynamic, but rather static so i can format the tableviews with settings like style.
in this settings view, i have a UITextField that takes a Person's name.
When the user clicks "Done" in the navigation bar, i want to pass that name back to the Master Root ViewController so i can use it in the title to display the Person's name. (I want to pass the name upon dismissing the view)
I have tried to use segue's but no luck. Here is a bit of what i tried.
SettingsViewController (pop's over the MasterVC)
class SettingsTableViewController: UITableViewController {
#IBOutlet weak var nameTextfield: UITextField!
#IBAction func done(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override viewDidLoad(){
self.title = "Settings"
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Trying to pass what was typed in the textfield to the Root ViewController and store this name in a variable of type String called 'name'
if segue.identifier == "masterView"{
let masterViewController = segue.destinationViewController as! MasterTableViewController
masterViewController.name = nameTextField.text
print("Segue was used")
}
}
}
MasterTableViewController.swift
class MasterTableViewController: UITableViewController {
var name: String!
// I am trying to then display the name entered in the pop-over view in this root view controller's title
override viewDidLoad(){
self.title = name!
}
}
My question is, what did i do wrong? I have tried to use delegation but i get nil and the data doesn't seem to get passed either way so any leads will greatly help. Thanks

There is a different kind of segue called Unwind Segue to do that. It doesn't create a new mvc and its used to pass data back to the vc that presented the current one. here is how to do it
First, go to your storyboard and control drag from settingVc to the exit button on top of it (to itself). it will give you 'Selection Segue' and choose IBAction goBack. This means any vc that presented the current one will get to prepare if they implement this method. In other words, you're putting out a protocol and the presenter vc will conform by implementing goBack IBAction. here is how to implement that. In your Mater vc
//You must have this function before you do the segue in storyboard
#IBAction func goBack(segue: UIStoryboardSegue){
/* this is optional
if let stv = segue.sourceViewController as? SettingsTableViewController{
self.name = stv.nameTextfield?.text
}
*/
}
In your setting vc (current vc)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//make sure you set the id of your segue to "Your Go back Unwind segue". ofc u can name it whatever
if segue.identifier == "Your Go back Unwind segue"{
if let mtvc = segue.destinationViewController as? MasterTableViewController{
mtvc.name = nameTextField.text
print("Segue was used")
}
}
}
and done method should be something like this
#IBAction func doneEditing(sender: UIButton) {
presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}

Related

Having trouble "reaching" my override prepare function to programmatically change views in a tab bar controller

I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234
My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("prepare reached")
//super.prepare(for: segue, sender: sender)
//switch(segue.identifier ?? "") {
//case "TabBar":
guard let TabController = segue.destination as? UITabBarController else {
fatalError("Unexpected destination: \(segue.destination)")
}
TabController.selectedIndex = toggle.selectedSegmentIndex
//default:
//fatalError("Unexpected Segue Identifier; \(segue.identifier)")
//}
}
#IBAction func toggleAction(_ sender: UISegmentedControl) {
print("toggle is now at index: ", toggle.selectedSegmentIndex)
//performSegue(withIdentifier: "TabBar", sender: sender)
//container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"
*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.
When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
tabBarVC.selectedIndex = sender.selectedSegmentIndex
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//called before mainvc viewDidLoad
let destinationVC = segue.destination as? UITabBarController
destinationVC?.selectedIndex = 1
}
}
You can't do what you are trying to do.
With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.
What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.
Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.

content of the viewcontroller changes according to the segue identifier

I have multiple segues from one viewController to another viewController. In the destinationViewController I have Label and Text View that changes according to the identifier. Of course, I can manage this without identifiers and just copy and paste viewControllers, and make my storyboard even bigger, write more code. But I want to learn to write good code. So, please help me to manage the issue.
I have such picture in my storyboard:
So, each button has its own identifier. I don't pass any data from ABOUT viewController, I just change the data inside History View Controller. That is why I need to solve the problem without using prepareForSegue function (because again I don't pass any data). And if I use prepareForSegue, then I will need to manage protocols, and that means more code.
You need to pass the identifier as a parameter from the calling ViewController in prepareForSegue.
See also: Can a viewcontroller access the identifier of an incoming segue?
it's a fast example how you can do it.
//specify enum
enum SegueType: Int {
case history, about, howto
}
class AboutVC: UIViewController {
//button pressing
#IBAction func didPressHistoryButton(sender: UIButton) {
let segueIdent = "show.History"
self.performSegue(withIdentifier: segueIdent, sender: SegueType.history)
}
// in your about vc
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationVC = segue.destination as? HistoryVC {
destinationVC.segueType = sender as? SegueType
}
}
}
// in your history vc
class HistoryVC: UIViewController {
var segueType: SegueType? = nil
override func viewDidLoad() {
super.viewDidLoad()
switch segueType! {
case SegueType.history:
// segue from history button
break
case SegueType.about:
// segue from about button
break
case SegueType.howto:
// segue from how to button\
break
default:
// segue from ???
break
}
}
}

How to perform segue from container view within a view displayed by navigation controller?

I want to segue from a view container within "H" that is presented using the navigation controller connected to the Split View Controller. How can I accomplish this? I have tried regular performSegueWithIdentifier using locally linked storyboard ID's but that removes the top navigation bar. I want to retain the top navigation bar and execute the segue as if it was done using the master navigation controller (rows that select which view controller is being presented in the detail view).
Any help is greatly appreciated!
Here is an example of how to perform a segue from an embedded ViewController.
ViewController.swift
import UIKit
protocol SegueHandler: class {
func segueToNext(identifier: String)
}
class ViewController: UIViewController, SegueHandler {
func segueToNext(identifier: String) {
self.performSegueWithIdentifier(identifier, sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destinationViewController as! HViewController
dvc.delegate = self
}
}
}
HViewController.swift
import UIKit
class HViewController: UIViewController {
weak var delegate: SegueHandler?
#IBAction func pressH(sender: UIButton) {
delegate?.segueToNext("GoToGreen")
}
}
Setup:
Use delegation to have the HViewController tell its embedding viewController to perform the segue.
Create a protocol called SegueHandler which just describes a class that implements the method segueToNext(identifier: String).
protocol SegueHandler: class {
func segueToNext(identifier: String)
}
Make your viewController implement this protocol by adding it to the class declaration line:
class ViewController: UIViewController, SegueHandler {
and by implementing the required function.
Add a delegate property to HViewController:
weak var delegate: SegueHandler?
Click on the embed segue arrow between ViewController and HViewController. Give it the identifier "EmbedH" in the Attributes Inspector.
Create a show segue between ViewController and the GreenViewController by Control dragging from the viewController icon at the top of ViewController to the GreenViewController. Name this segue "GoToGreen" in the Attributes Inspector.
In prepareForSegue for ViewController, when the "EmbedH" segue happens, set the delegate property of HViewController to self (ViewController).
When the user clicks the H button in the HViewController, call delegate?.segueToNext("GoToGreen") to trigger the segue in ViewController.
Here it is running in the simulator:
I was needing exactly what #vacawama proposed here, though I couldn't reproduce that, I tried exactly your steps but self.delegate?.segueToNext("GoToGreen") got called but neither the protocol itself nor the container view controller. After an entire day searching about this approach I realized the problem was with the swift version. Just replace this:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destination as! HViewController
dvc.delegate = self
}
}
for this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destination as! HViewController
dvc.delegate = self
}
}
Other detail I was missing was about the embedded segue. Be sure to connect the container View to the HViewController, not the View Controller itself, otherwise the Embed option for segue won't appear.

How can i create a segue that pass data to another view controller only if certain criteria are met

I want to create a segue to pass data to another view controller but there are certain criteria that must happen for the segue to happen. If possible i would prefer to use the segue Id instead of the dragging method.
this is an example Im trying to accomplish
#IBAction func SubmitButtonPressed(sender: AnyObject) {
if 1<0 {
// dont perform segue
}else{
//Perform segue
// i want to pass this data in the next VC
var data = "foo"
//this is my segue id i want o use to go to the Second VC
var segueId = "segueForgotPasswordTwo"
// second VC
var secondVc = "viewController2"
// Iwant to to use prepare for segue but im getting errors in the parameters
prepareForSegue(UIStoryboardSegue, sender: AnyObject?){
}
}
}
Your question is a bit unclear but I believe this is what you are looking for...
func someFunction(){
if //some condition {
//code
}else if //some condition {
//code
} else {
//perform segue by using the folowing line. Assign the identifier to the segue in the storyboard.
//Do this by first creating a segue by dragging from a view controller to the destination view controller. Be sure to drag from the VIEWCONTROLLER, to the destination VIEWCONTROLLER. DO NOT just drag from the button. Next, choose the type of segue (eg. show or present modally), and then type in an indentifier for this segue.
performSegueWithIdentifier("SegueIdentifier", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueIdentifier" {
//now find your view controller that you are seguing to.
let controller = segue.destinationViewController as! SomeViewController
//access the properties of your viewController and set them as desired. this is how you will pass data along
controller.property = someValue
}
}
Overview:
Hook the segue from the source view controller to the destination view controller (see left side red arrows)
Don’t hook it from the button to the destination view controller
Create an action for the button to do your custom condition check then perform segue
Screenshot:
Code:
var data = "foo"
#IBAction func buttonPressed(sender: UIButton) {
let someCondition = true
if someCondition {
performSegueWithIdentifier("showGreen", sender: self)
}
else {
performSegueWithIdentifier("showPink", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showGreen" {
let greenVC = segue.destinationViewController as! GreenViewController
// Make sure the data variable exists in GreenViewController
greenVC.data = data
}
}
You can implement the shouldPerformSegueWithIdentifier function in your ViewController. When the segue is triggered, this function can cancel the segue if it returns false, so you can simply include whatever logic is required in this function and return true/false as appropriate.

How to make a button to do some operation or change to another view?

help me please. how to make a button to do some operation or change to another view? I want that by pressing on it, commands were complete then need that moved on another view. please tell me how else to do to another kind in which a transition is updated when you press the button, all the same. And there were set data from datamodel
#IBAction func addNew(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Items", inManagedObjectContext:managedObjectContext!)
var item = Items(entity: entity!, insertIntoManagedObjectContext:managedObjectContext)
// I make the init from row coredata
item.titleIt = titleItem.description
item.textIt = textViewItem.description
var error: NSError?
managedObjectContext?.save(&error)
// MasterViewController.setValue(Items(), fromKey: "titlIt")
}
Add this at the end of your method:
let viewControllerToGoTo = ClassNameOfViewController()
self.presentViewController(viewController, animated: true, completion: nil)
Note: You may need to instantiate the view controller you want to present in a different way than just (). For example, you may want to load a view controller defined in a nib or storyboard.
To move to another view controller, you write these lines:
var viewController = ViewControllerNew()
self.presentViewController(viewController, animated: true, completion: nil)
But if you want to pass data to another view, I'd recommend using this method instead:
In your storyboard, select the view controller and drag from the blue-outlined yellow square at the top of the view controller to another view controller. A popup will appear, showing a list of segues. Select the item named 'Show'. Segues are placeholders for presenting view controllers -- you can call them at any time in your program. Select the segue, and under the menu on the right hand side type a name, for example 'toOtherScreen' into the text field labeled 'Identifier'. This will let you call the segue from a specific name.
Now go back to your IBAction for the button, and type this:
self.performSegueWithIdentifier("toOtherScreen", sender: self)
This will transition to the other screen. But what if you want to pass data to a screen, or do something when a segue is about to happen? Luckily for you, UIViewController class has a method named 'prepareForSegue', as shown below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toOtherScreen" {
println("yes")
}
}
In this, we check if the segue has an identifier of "toOtherScreen", and if so, print "yes" to the logs. Now, to pass data to another view controller, it is a little more tricky. In the other view controller file, add a variable at the start of the class:
class OtherViewController: UIViewController {
var dataPassed: String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
And in your prepareForSegue method in the main view controller, type this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toOtherScreen" {
let newViewController = segue.destinationViewController as OtherViewController()
newViewController.dataPassed = "NEW DATA"
}
}
Now it will change the variable named dataPassed in OtherViewController to 'NEW DATA'. You can see how you can achieve a lot through this simple way of passing data to other view controllers.
Hope I helped.

Resources