How to pass a data to a nested controller (UIContainerView)? - ios

I've searched everywhere for a solution but nothing. I have two view controllers and I want to pass data from viewController.swift to resultViewController.swift (the container view), i've succeeded to send data when I run it. but when i clicked increase button I can't send data again.
ViewController.swift:
class ViewController: UIViewController {
var result: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// performSegueWithIdentifier("sendResult", sender: <#AnyObject?#>)
}
#IBAction func increas(sender: AnyObject) {
result++
performSegueWithIdentifier("sendResult", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sendResult"{
var resultVC = segue.destinationViewController as! ResultViewController
resultVC.result = self.result
}
}
}
ResultViewController.swift:
class ResultViewController: UIViewController {
var result: Int!
#IBOutlet weak var resultLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
resultLabel.text = "\(result)"
}
}

Ok. Here's some working code.
First, remove your increase method and unhook it from your button in the Connections Inspector. Then delete your old segue in Interface Builder and create a new segue by dragging from the button to ResultsViewController. Make sure you give the segue a "sendResult" identifier in Interface Builder. In ViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sendResult" {
result++
var rvc = segue.destinationViewController as! ResultViewController
rvc.result = result
}
}
And then in ResultsViewController:
#IBOutlet weak var resultLabel: UILabel!
var result : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
resultLabel.text = "\(result)"
// Do any additional setup after loading the view.
}

Nested ViewControllers are a bit tricky to use because from the parent controller point of view, all they see is a UIView (there is no rootViewController or nestedViewController property, which would be very nice to have).
The best way I have found to send/read data to/from the nested controller is by adding a reference to it on the parent controller.
It turns out that prepareForSegue is called after viewDidLoad for all nested view controllers. Then it is never called again, unless you call it manually. But you shouldn't and will not need to do that.
Here is how you can get a reference to the nested controller to use later in your code:
class ViewController: UIViewController {
var result: Int = 1
private var resultVC: ResultViewController? // Keep it optional
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func increas(sender: AnyObject) {
result++
// Update result value, keep optional reference for security
// Here you could write & read values, call methods, etc.
resultVC?.result = result
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sendResult" {
// Save reference to child view controller (use optional)
resultVC = segue.destinationViewController as? ResultViewController
}
}
}
Once you have the reference to the nested view controller, you can use it anywhere in the code. To be safe, always use it with ?.

Related

Update label in ViewControllerB from ViewControllerA (ViewControllers are both in containers in the same view)

View controllers A and B are both in containers and together form a single view.
In ViewControllerA I have a button and a label, and in ViewControllerB I have a label.
Both labels are initialised to the number "5".
By pressing on the button in ViewControllerA I would like to add 3 to each label,
i.e. each label should then display "8".
I thought it was as simple as defining a function in ViewControllerB to accept the updated total from ViewControllerA, and to then update the text property of the label in ViewControllerB.
Of course, I get "unexpectedly found nil while unwrapping an Optional value".
Advice/guidance greatly appreciated.
import UIKit
class ViewControllerA: UIViewController {
//MARK: Properties
#IBOutlet weak var buttonInViewControllerA: UIButton!
#IBOutlet weak var labelInViewControllerA: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: Actions
#IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
let a: String = String(Int(labelInViewControllerA.text!)! + 3)
labelInViewControllerA.text = a
ViewControllerB().add3(value: a)
}
}
class ViewControllerB: UIViewController {
//MARK: Properties
#IBOutlet weak var labelInViewControllerB: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func add3(value: String) {
self.labelInViewControllerB.text = value
}
}
The problem is that in
#IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
// ...
ViewControllerB().add3(value: a)
}
you create a new instance of ViewControllerB. What you need is a reference (property) to the existing one, which you then inform about the change:
class ViewControllerA: UIViewController {
var controllerB:ViewControllerB?
// ...
#IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
// ...
controllerB?.add3(value: a)
}
}
And dont't forget to set controllerB somewhere in your code, e.g.
var vcA = ViewControllerA()
var vcB = ViewControllerB()
vcA.controllerB = vcB
// dispaly vcA and vcB
You likely want to use the Protocol / Delegate pattern (good idea to read up on it if you're not familiar).
Basically, you create a Protocol that defines a function (or functions) that you want to access from "somewhere else."
For example:
// protocol / delegate pattern
protocol ABDelegate: class {
func myButtonWasTapped()
}
You want to call myButtonWasTapped() from the button tap in A, so you create a view controller that "conforms" to this protocol (it has that func), and an ABDelegate variable in the view controller where you want to call the func.
Your first thought might be:
"ok, I'll set that up so vcB is the delegate for vcA and have my button tap func in vcA call the increment func in vcB directly."
That can work, but then the two classes are "too tightly coupled." That is, too dependent on each other.
The better approach is to have your "main" view controller act as an intermediary:
// this will be called by the code in ViewControllerA
func myButtonWasTapped() {
// call func in ViewControllerB
vcB?.doIncrement()
}
So it looks like this:
The tricky part is getting references to vcA and vcB. Because the embedded view controllers in container views are loaded via embed segues you can grab them in prepare(for segue:...):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// if this is the embedded ViewControllerA
if let vc = segue.destination as? ViewControllerA {
// set self as the ABDelegate in ViewControllerA
vc.theDelegate = self
// keep a reference in case we want to access it
self.vcA = vc
}
// if this is the embedded ViewControllerB
if let vc = segue.destination as? ViewControllerB {
// keep a reference so we can call its doIncrement() func
self.vcB = vc
}
}
The full code becomes:
//
// ContainerTestViewController.swift
//
import UIKit
// protocol / delegate pattern
protocol ABDelegate: class {
func myButtonWasTapped()
}
// "main" view controller conforms to ABDelegate protocol
class ContainerTestViewController: UIViewController, ABDelegate {
var vcA: ViewControllerA?
var vcB: ViewControllerB?
// this will be called by the code in ViewControllerA
func myButtonWasTapped() {
// call func in ViewControllerB
vcB?.doIncrement()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// if this is the embedded ViewControllerA
if let vc = segue.destination as? ViewControllerA {
// set self as the ABDelegate in ViewControllerA
vc.theDelegate = self
// keep a reference in case we want to access it
self.vcA = vc
}
// if this is the embedded ViewControllerB
if let vc = segue.destination as? ViewControllerB {
// keep a reference so we can call its doIncrement() func
self.vcB = vc
}
}
}
class ViewControllerA: UIViewController {
var theDelegate: ABDelegate?
#IBAction func incTapped(_ sender: Any) {
// call the func in the delegate
theDelegate?.myButtonWasTapped()
}
}
class ViewControllerB: UIViewController {
#IBOutlet var theLabel: UILabel!
var theValue = 0
override func viewDidLoad() {
super.viewDidLoad()
theLabel.text = String(theValue)
}
func doIncrement() -> Void {
theValue += 3
theLabel.text = String(theValue)
}
}

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.

Pass data backward from detailViewController to masterViewController

I am trying to pass data back from the second viewController.
I can do that without NavigationController. But now I need to use NavigationController. Then my code does work as before. The data wont pass.
Here is the simple code:
In first viewController
class ViewController: UIViewController, backfromSecond {
#IBOutlet weak var text: UILabel!
var string : String?
override func viewDidLoad() {
super.viewDidLoad()
self.string = "Start here"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.text.text = self.string
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? secondViewController{
destinationViewController.delegate = self
}
}
func back(text: String) {
self.string = text
print(text)
}
}
And Second viewController:
protocol backfromSecond {
func back(text: String)
}
class secondViewController: UIViewController {
var string : String = "nothing here"
var delegate : backfromSecond?
override func viewDidLoad() {
super.viewDidLoad()
delegate?.back(text: string)
// Do any additional setup after loading the view.
}
}
What is wrong here?
Suppose A & B are two controllers and you first navigated from A to B with some data. And now you want to POP from B to A with some data.
Unwind Segues is the best and recommended way to do this.
Here are the steps.
Open A.m
define following method
#IBAction func unwindSegueFromBtoA(segue: UIStoryNoardSegue) {
}
open storyboard
Select B ViewController and click on ViewController outlet. press control key and drag to 'Exit' outlet and leave mouse here. In below image, selected icon is ViewController outlet and the last one with Exit sign is Exit Outlet.
You will see 'unwindSegueFromBtoA' method in a popup . Select this method .
Now you will see a segue in your view controler hierarchy in left side. You will see your created segue near StoryBoard Entry Piont in following Image.
Select this and set an identifier to it. (suggest to set the same name as method - unwindSegueFromBtoA)
Open B.m . Now, wherever you want to pop to A. use
self.performSegueWithIdentifier("unwindSegueFromBtoA", sender: dataToSend)
Now when you will pop to 'A', 'unwindSegueFromBtoA' method will be called. In unwindSegueFromBtoA of 'A' you can access any object of 'B'.
That's it..!
I think your problem is in the prepare for segue method. If the view controller is on a navigation stack i think your code should be something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? UINavigationController).topViewController as! secondViewController{
destinationViewController.delegate = self
}
}
You can use unwind segues to pass data back.
Here's a tutorial
https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
This works me well.
1st VC
class ViewController: UIViewController, backfromSecond {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func Passingfrom1stVCTo2ndVC(_ sender: AnyObject) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as? ViewController3{
vc.dataFrom1StVC = "message send from 1st VC"
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
}
func back(text: String) {
print("data\(text)")
}
}
2nd VC.
protocol backfromSecond: class {
func back(text: String)
}
class ViewController3: UIViewController {
var dataFrom1StVC : String? = nil
week var delegate : backfromSecond?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func DataSendFrom2ndVCTo1stVC(_ sender: AnyObject) {
self.delegate?.back(text: "Message Send From 2nd vc to 1st VC")
self.navigationController?.popViewController(animated: true)
}
}
I hope it will work you. If any problem then ask me i will help you.

Segue programmatically does not work in viewDidLoad

I have been trying to perform a segue programmatically by doing:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var btnRedirect: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.performSegueWithIdentifier("segueToSecond", sender: btnRedirect)
}
#IBAction func redirect(sender: AnyObject) {
self.performSegueWithIdentifier("segueToSecond", sender: self)
}
}
The problem is that the segue works perfectly by clicking in the button to perform it although when I try to do it programmatically as you can see inside the viewDidLoad method, it does not work!
I've already tried to pass sender as nil.
As dan'S comment you can't perform segue in ViewDidLoad do it in viewDidAppears.?
Note: Move perform segue from your ViewDidLoad to viewDidAppers or inside your button Action as below code and make sure you set your segue identifier in storyBoard to "segueToSecond".Untested code.So,let me know.if you have any trouble executing.
Updated:
Note: If you want your segue perform automatically after view loads. you should connect segue from VC to VC.
#IBAction func redirect(sender: AnyObject) {
segueToSecondVC()
}
func segueToSecondVC() {
self.performSegue(withIdentifier: "segueToSecond", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueToSecond") {
let dest = segue.destination as! viewTwo // viewTwo is your destination ViewController
print("Segue Performed")
}
}
Updated:
Note: If you want the segue perform automatilcally after view loads you can setup some delay to your segue from below code.
let delayInSeconds = 4.0
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
segueToSecondVC()
}
}
Hope this help you to resolve the issue....

action without button on swift

I’m new at this and I’m wondering if it’s possible in swift or objective-C to make an action without the interaction of a button or any type of user interaction? So far i can send an array to a second ViewController but I need to process the data and show it in a UIPickerview and I don’t know how...
i pass the data with this:
func pepareSegueWithIdentifier(segue:UIStoryboardSeque, senderAnyObject?){
var detailVC = segue.destinationViewController as ViewNotLogged;
detailVC.pickerData1 = pickerData
}
i creater the var in the second viewcontroller
class ViewNotLogged: UIViewcontroller, UIPickerViewDataSource, UIPickerDataDelegate{
var pickerData1=[String]()
}
but after this i dont nowaht to do to modify the data or even check it with println()
For my comment above - you could do something like this:
class ViewNotLogged: UIViewController {
var pickerData1 = [String]()
override func viewDidLoad() {
super.viewDidLoad()
println("My data \(pickerData1)")
}
}
And try not to implements UIPickerViewDataSource, UIPickerViewDelegate before VC don't ready
UPD:
Emulate with my data and it works.
Here my code:
class ViewController: UIViewController {
var pickerData = ["Sw", "Sw"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var detailVC = segue.destinationViewController as ViewNotLogged;
detailVC.pickerData1 = pickerData
}
}
class ViewNotLogged: UIViewController {
var pickerData1 = [String]()
override func viewDidLoad() {
super.viewDidLoad()
println("My data \(pickerData1)")
}
}
In IB I just have two VC first is ViewController and second is ViewNotLogged. Also I have button at first, that make modal storyboard segue.

Resources