Passing array through segue in swift - ios

I've been struggling for a few days now with passing an array from my SecondViewController to my FirstViewController using Swift.
From my research I found that segue with prepareForSegue would be a viable option but I can't seem to figure it out. What am I doing wrong?
My prepareForSegue in SecondViewController looks like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DVC: FirstViewController = segue.destinationViewController as! FirstViewController
DVC.mySeguedArray = myIncomeArray
}
My FirstViewController looks like this:
class FirstViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
var myIncomeArray: [Income] = []
var mySeguedArray: [Income] = []
override func viewDidLoad() {
super.viewDidLoad()
myIncomeArray = mySeguedArray
}
Also worth mentioning is that I'm sure I'm populating the array in the SecondViewController as I'm testing it by printing out the size of the array when adding new Income objects to it. But the array in FirstViewController just returns 0 when checking it size after segue.
Edit: Added picture of storyboard
StoryBoard

Did you know that array in Swift are value type. So, when you assign array to some other variable or pass it to some method, the value is passed; not the reference to actual object. When you pass your array to second view controller, it copies the content of your original array. You are then adding to this array which do not change the content of original array.
I would suggest you to create some kind of callback or delegate pattern from second view controller to first view controller to inform the changes such that first view controller would also modify the original array content.
Here is a small portion of code that will help you understand this,
let originalArray = ["a", "b", "c"]
var newArray = originalArray
newArray.append("d")
print(originalArray) // prints ["a", "b", "c"]
print(newArray) // prints ["a", "b", "c", "d"]

You can use protocol to pass data between view controllers
protocol YourFirstViewControllerDelegate: class {
func onButtonClicked(yourString: [String])
}
For passing data from first view controller to second in your Main view Controller when you want to show your first view controller:
you should set main view controller to your view controller delegate, yourFirstViewController.setDlegate(self)
In FirstViewController you should raise an event to main with onButtonClicked([String])
In onButtonClicked([String]) you can call secondViewController and pass your array with onSecondViewController([String])
class MainViewController: UIViewController,YourFirstViewControllerDelegate{
var yourString = [String]()
weak var vcFirst: FirstViewController?
weak var vcSecond: SecondViewController?
func onFirstViewController(){
if (self.vcFirst == nil){
self.vcFirst = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as? FirstViewController
self.vcFirst!.view.translatesAutoresizingMaskIntoConstraints = false
self.vcFirst?.setDelegate(youeFirstViewControllerDelegate: self)
}
self.setActiveController(viewController: self.vcFirst)
}
func onSecondViewController(yourString:[String]){
if (self.Second == nil){
self.Second = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
self.vcSecond!.view.translatesAutoresizingMaskIntoConstraints = false
self.vcSecond.setArray(yourArray: self.yourArray)
}
self.setActiveController(viewController: self.vcSecond)
}
....// your view controller method
func onButtonClicked(yourString: [String]){
self.yourString = yourString
onSecondViewController(yourString:self.yourString)
}
In your First view controller
class FirstViewController: UIViewController{
weak var yourFirstViewControllerDelegate: YourFirstViewControllerDelegate?
var yourString = ["test1","test2"]
// all your overide method should be here
func setDelegate(yourFirstViewControllerDelegate: YourFirstViewControllerDelegate){
self.yourFirstViewControllerDelegate = yourFirstViewControllerDelegate
}
func onButtonClicked(){
self.yourFirstViewControllerDelegate.onButtonClicked(self.yourString)
}
In your second view controller
class SecondViewController: UIViewController{
var yourString = [String]
// all your overide method should be here and you can use your [String]
func setArray(yourString:[String]){
self.yourString = yourString
}
}
I have not Mac OS to check my code now please just read this code not copy, I'll edit this code tomorrow

Your code looks fine, use the following prepareforsegue as it helps figure out where the problem could be
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueIdInStoryboard" {
if let DVC = segue.destinationViewController as? FirstViewController{
DVC.mySeguedArray = myIncomeArray
} else {
print("Data NOT Passed! destination vc is not set to firstVC")
}
} else { print("Id doesnt match with Storyboard segue Id") }
}
class FirstViewController: UIViewController
{
#IBOutlet weak var textView: UITextView!
var myIncomeArray: [Income]!
var mySeguedArray: [Income]!{
didSet{
myIncomeArray = mySeguedArray //no need to call viewDidLoad
}
}
}

Related

Swift Protocol Third VC to First VC

I need to pass a String and Array from my Third ViewController to my First ViewController directly using protocol/delegate, I have no problem doing it from VC 2 to VC 1 but I'm having a hard time with this. Also after clicking a button in my VC3 I need to go back to VC 1 and update the VC UI how would I do that? Would that have to be in viewdidload?
This in Swift UIKit and Storyboard
You need two protocols, and your firstVC and SecondVC have to conform those. When pushing new ViewController you need to give the delegate of that ViewController to self. On your third VC, when you click the button you need to call your delegate and pass your data to that delegate method, then repeat the same for other.
For FirstVC
protocol FirstProtocol: AnyObject {
func firstFunction(data: String)
}
class FirstVC: UIViewController, FirstProtocol {
weak var delegate: FirstProtocol?
#IBAction func buttonClicked(_ sender: Any) {
let secondVC = SecondVC()
secondVC.delegate = self
navigationController?.pushViewController(secondVC, animated: true)
}
func firstFunction(data: String) {
navigationController?.popToRootViewController(animated: true)
print(data)
}
}
You handle your navigation from your root. For better experience you can use something like coordinator pattern to handle it.
protocol SecondProtocol: AnyObject {
func secondFunction(data: String)
}
class SecondVC: UIViewController, SecondProtocol {
weak var delegate: FirstProtocol?
#objc func buttonClicked() {
let thirdVC = ThirdVC()
thirdVC.delegate = self
navigationController?.pushViewController(thirdVC, animated: true)
}
func secondFunction(data: String) {
delegate?.firstFunction(data: data)
}
}
Second VC is something that you just need to pass parameters.
class ThirdVC: UIViewController {
weak var delegate: SecondProtocol?
#objc func buttonClicked() {
delegate?.secondFunction(data: "data") // PASS YOUR ARRAY AND STRING HERE
}
}
What you need is unwind segue. Unwind segue will act like segue, only backward, popping, in this case, VC2. You can read here for more information.
Updating data code would be put in a function similar to prepareToSegue() but for unwind segue in your VC1.
Example of the function inside VC1:
#IBAction func unwindToDestination(_ unwindSegue: UIStoryboardSegue) {
switch unwindSegue.identifier {
case SegueIdentifier.yourSegueIdentifier:
let sourceVC = unwindSegue.source as! SourceVC
dataToPass = sourceVC.dataToPass
reloadData()
default:
break
}
}
Here is a different approach that accomplishes what you described by performing a Present Modally segue directly from View Controller 3 to View Controller 1, and sharing the string and array values by way of override func prepare(for segue....
In Main.storyboard, I set up 3 View Controllers, and have segues from 1 to 2, 2 to 3, and 3 to 1. These are Action Segues directly from the buttons on each VC, which is why you won't see self.performSegue used inside any of the View Controller files. Here is the picture:
In the first view controller, variables are initialized (with nil values) that will hold a String and an Array (of type Int in the example, but it could be anything):
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var updatableTextLabel: UILabel!
var string: String?
var array: [Int]?
override func viewDidLoad() {
super.viewDidLoad()
// These will only not be nil if we came here from the third view controller after pressing the "Update First VC" button there.
// The values of these variables are set within the third View Controller's .prepare(for segue ...) method.
// As the segue is performed directly from VC 3 to VC 1, the second view controller is not involved at all, and no unwinding of segues is necessary.
if string != nil {
updatableTextLabel.text = string
}
if let a = array {
updatableTextLabel.text? += "\n\n\(a)"
}
}
}
The second view controller doesn't do anything except separate the first and third view controllers, so I didn't include its code.
The third view controller assigns the new values of the string and array inside prepare (this won't be done unless you press the middle button first, to demonstrate both possible outcomes in VC 1). This is where your string and array get passed directly from 3 to 1 (skipping 2 entirely).
import UIKit
class ThirdViewController: UIViewController {
var theString = "abcdefg"
var theArray = [1, 2, 3]
var passValuesToFirstVC = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func updateFirstVC(_ sender: UIButton) {
passValuesToFirstVC = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if passValuesToFirstVC && segue.identifier == "toFirst" {
// Cast segue.destination (which on its own has type UIViewController, the superclass to all your custom View Controllers) to the specific subclass that your first View Controller belongs to
let destinationVC = segue.destination as! FirstViewController
// When your first view controller loads, it will receive these values for the 'string' and 'array' variables. They replace the original 'nil' values these had in the FirstViewController definition.
destinationVC.string = theString
destinationVC.array = theArray
}
}
}
Note that there is an IBOutlet to the label on the first View Controller which contains the text to be updated.
After visiting the third view controller, pressing the "Update First VC Text" button, and then performing the segue back to the first, here is how it will look:
This doesn't address the part about protocols and delegates in your question (as I'm not sure how they're being used in your program, and other answers have already addressed that), but it illustrates the method of transferring variables directly from one View Controller to another without unwinding segues or using the UINavigationController.

Calling methods in child view controller in iOS

I have an app written in Swift for iOS 13 where I present a view modally using storyboards. Once the new view is being presented, I want the parent to call a method which is located inside the child view controller (of my custom class which inherits from UIViewController).
To do this, I plan to have a method inside my parent view controller that gets the modal view controller being presented as its child. Once I get this reference, I will call the child's function from my parent view controller.
I realise this is probably a bad design decision, but I haven't found a way to avoid this approach. I have looked all over stackoverflow to find an answer, but I haven't found any yet. Any help would be much appreciated.
You can instantiate the child view controller and set its properties before presenting it. Then the code that changes the child view controller based on the data is put in the viewDidLoad() method.
class ParentViewController: UIViewController {
func goToChildViewController(object: CustomObject) {
guard let childViewController = self.storyboard?.instantiateViewController(withIdentifier: "child") as? ChildViewController else { fatalError("Cannot instantiate child view controller!") }
childViewController.myProperty = true
childViewController.myObject = myObject // Example of how to pass data from a data model
self.present(childViewController, animated: true)
}
}
class ChildViewController: UIViewController {
var myProperty = false
var myObject: CustomObject? = nil
override viewDidLoad() {
if myProperty {
// Conditional code here
}
{
}
Alternatively, you could trigger a segue in code instead of presenting the child view controller directly.
In this case, you would set up the child view controller inside the parent view controller’s overridden prepare(for:sender:) method, where the child view controller can be accessed using segue.destinationViewController.
Through Segue
When segue triggered maybe through a button press or a table view selection prepare(for:) method will be called on your view controller, at this point you can configure your DestinationViewController by setting some properties.
RootViewController.Swift
#IBOutlet weak var textFieldFirstName: UITextField!
#IBOutlet weak var labelFullname: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let firstVC = segue.destination as? FirstViewController else { return }
firstVC.firstname = textFieldFirstName.text
}
After typing the firstname and tap enter button , firstname value is passed to firstViewController and assigned to related UILabel in viewDidLoad() Method.
FirstViewController.Swift
#IBOutlet weak var textFieldLastName: UITextField!
#IBOutlet weak var labelFirstName: UILabel!
var firstname: String?
override func viewDidLoad() {
super.viewDidLoad()
labelFirstName.text = “Firstname: \(firstname ?? “”)”
}
You can achieve same thing through closure and Delegates

Pass data between ViewController without Segues

I am pretty new to swift development and have some problems understanding how to pass data between ViewController.
I want to build a simple music player app which has three views (Player, Playlists, Tracks).
At start the Player is shown to the user. From there the user can press a button and the Playlists view come up. Now he can select a playlist and the next view Tracks is displayed.
If he press on a track he gets back to the Player view and the track is playing. So I need to pass my track to my PlayerViewController.
Currently I'm using Segues to display each ViewController.
Player -> Playlists -> Tracks -> Player
But this will initialise the Player again which means that values/variables get reset. How can I avoid this?
If you are getting from view controller B to view controller C by saying present, then view controller C can speak to view controller B as its presentingViewController.
Try using Unwind segues to pass data i guess they can help you out.
An unwind segue (sometimes called exit segue) can be used to navigate
back through push, modal or popover segues (as if you popped the navigation
item from the navigation bar, closed the popover or dismissed the modally
presented view controller). On top of that you can actually unwind through
not only one but a series of push/modal/popover segues, e.g. "go back"multiple steps in your navigation hierarchy with a single unwind action.When you perform an unwind segue, you need to specify an action, which is an action method of the view controller you want to unwind to.
//ViewControllerA:
import UIKit
class ViewControllerA: UIViewController {
var dataRecieved: String? {
willSet {
labelOutlet.text = newValue
}
}
#IBOutlet weak var labelOutlet:UILabel!
#IBOutlet weak var nextButtonOutlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func nextButtonAction(_ sender:UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB
controller.dataPassed = labelOutlet.text
self.present(controller, animated: true, completion: nil)
}
// segue ViewControllerB -> ViewControllerA
#IBAction func unwindToThisView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ViewControllerB {
dataRecieved = sourceViewController.dataPassed
}
}
}
//ViewControllerB
import UIKit
class ViewControllerB: UIViewController , UITextFieldDelegate{
#IBOutlet weak var textFieldOutlet: UITextField!
var dataPassed : String?
override func viewDidLoad() {
super.viewDidLoad()
textFieldOutlet.text = dataPassed
textFieldOutlet.delegate = self
}
// UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// User finished typing (hit return): hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
dataPassed = textField.text
}
}
//From the Return Button click control and drag to exit of the viewcontrollerB as show in the image.
To pass data between View Controllers, have this block in your code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let feed = segue.destination as! SecondViewControllerName
feed.variableinsecondviewcontroller = variableincurrentviewcontroller
}
If that didn't help, you might need to elaborate on what exactly you want with your code...
Use struct class and access your object any where using the stuct object
Like this
struct SomeStruct { var name: String init(name: String) { self.name = name } } var aStruct = SomeStruct(name: "Bob") var bStruct = aStruct // aStruct and bStruct are two structs with the same value! bStruct.name = "Sue" println(aStruct.name) // "Bob" println(bStruct.name) // "Sue"

View outlet nil after segue

I am working on the online Stanford IOS development course and am having a bug I don't know how to deal with. When I segue to the detail view in the split view controller I am able to set parameters of the detail UIView from the detail UIViewController in the didSet of outlet to the view. Later when I try to access or mutate the detail view from the controller the view variable(outlet) is nil even though the view is still on screen and responds to gestures.
This is the prepare method of the master view in the split view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationViewController = segue.destination
if let graphViewController = destinationViewController as? GraphViewController {
if let identifier = segue.identifier{
switch identifier {
case "DisplayGraph":
graphViewController.function = funcForGraphView(_:)
default:
break
}
}
}
}
This is the detail view controller. I commented on the interesting parts.
class GraphViewController: UIViewController {
#IBOutlet weak var graphView: GraphView!{
didSet{
graphView.addGestureRecognizer(UITapGestureRecognizer(target: graphView, action: #selector(graphView.tap(recognizer:))))
graphView.addGestureRecognizer(UIPanGestureRecognizer(target: graphView, action: #selector(graphView.pan(recognizer:))))
graphView.addGestureRecognizer(UIPinchGestureRecognizer(target: graphView, action: #selector(graphView.zoom(recognizer:))))
graphView.function = cos //I can set variables in the view here
// After this didSet I am no longer able to set or read any variables from the graphView
}
}
var function: ((Double)->(Double))? {
didSet{
if let newfunction = function {
graphView.function = newfunction // I can not set or access variables in the view here since for some reason graphView is nil
}
}
}
And this is the relevant variable in the detail view
var function: ((Double) -> Double)? {
didSet{
setNeedsDisplay()
}
}
Storyboard Picture
Any help would be greatly appreciated and hopefully this isn't a stupid question. If more information would be helpful let me know.
Thanks!
It will be nil because of your view not fully loaded while your are push viewcontrolle. so you needs to pass as below :
In GraphViewController
var yourValue : ((Double)->(Double))?
override func viewDidLoad() {
super.viewDidLoad()
self.function = yourValue
}
Set value while navigation
if let identifier = segue.identifier{
switch identifier {
case "DisplayGraph":
graphViewController.yourValue = funcForGraphView(_:)
default:
break
}
}

passing data from one tab controller to another in swift

My application has three viewController associated with three tab of tabBar. I want to switch from 1st tab to 3rd tab and pass some data from 1st view controller to 3rd view controller. I can't do it with segue, because segue create navigation within the selected tab. That is not my requirement. I want to switch tab in tabBar and pass some data without any navigation. How can i do it ?
Swift 3 in latest Xcode version 8.3.3
First you can set a tab bar delegate UITabBarControllerDelegate in FirstViewController. You can use this when you want to send data from one view controller to another by clicking the tab bar button in UITabBarViewController.
class FirstViewController: UIViewController ,UITabBarControllerDelegate {
let arrayName = ["First", "Second", "Third"] 
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController.isKind(of: FirstsubsecoundViewController.self as AnyClass) {
let viewController = tabBarController.viewControllers?[1] as! SecondViewController
viewController.arrayData = self.arrayName
} 
return true
}
}
Get data in SecondViewController:
class SecondViewController: UIViewController {
var arrayData: [String] = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
print(arrayData) // Output: ["First", "Second", "Third"]
}
}
you can use this code : Objective C
[tab setSelectedIndex:2];
save your array in NSUserDefaults like this:
[[NSUserDefaults standardUserDefaults]setObject:yourArray forKey:#"YourKey"];
and get data from another view using NSUserDefaults like this :
NSMutableArray *array=[[NSUserDefaults standardUserDefaults]objectForKey:#"YourKey"];
swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toTabController" {
var tabBarC : UITabBarController = segue.destinationViewController as UITabBarController
var desView: CaseViewController = tabBarC.viewControllers?.first as CaseViewController
var caseIndex = overviewTableView!.indexPathForSelectedRow()!.row
var selectedCase = self.cases[caseIndex]
desView.caseitem = selectedCase
}
}
Okay, i tried with creating a singleton object in viewController of first tab and then get that object's value from viewController of third tabBar. It works only for once when third tab's view controller instantiates for the 1st time. I never got that singleton object's value in third tab's view controller except the first time. What can i do now ? In my code - In first tab's controller, if i click a button tab will be switched to third tab. Below is my code portion -
In First tab's controller -
#IBAction func sendBtnListener(sender: AnyObject) {
Singleton.sharedInstance.brandName = self.searchDisplayController!.searchBar.text
self.tabBarController!.selectedIndex = 2
}
In Third tab's Controller -
override func viewDidLoad() {
super.viewDidLoad()
//Nothing is printed out for this portion of code except the first time
if !Singleton.sharedInstance.brandName.isEmpty{
println(Singleton.sharedInstance.brandName)
}else{
println("Empty")
}
}
In Singleton Object's class -
class Singleton {
var name : String = ""
class var sharedInstance : Singleton {
struct Static {
static let instance : Singleton = Singleton()
}
return Static.instance
}
var brandName : String {
get{
return self.name
}
set {
self.name = newValue
}
}
}
Edited :
Okay at last it's working. For others who just want like me, please replace all the code from viewDidLoad() to viewWillAppear() method in third tab's (destination tab) controller and it will work. Thanks.

Resources