I have some quite simple doubt regarding optional binding,global variable & wrapping and unwrapping . Since I am new to SWIFT, its very important to understand the tits
I'll start by repeating the my comment from above.
Possibly you've misunderstanding the concept of global variables in Swift.
If you have a global variable, you won't have to "pass" it between any views/methods/classes etc, because the variable is defined at global scope (accessible everywhere).
Generally global variables is not a good idea, and something that you want to avoid.
Regarding the matter of global variables and swift, you really should include singletons into the discussion. See e.g. the following existing SO thread(s):
(How to create a global variable?)
(Declaring Global Variables in Swift)
Communication between TableViewController and ViewController by means of segues (prepare for & unwind segues)
(This answer ended up being very and probably a bit too thorough, as I didn't know in detail what your current tableview/viewcontroller program state looks like. Sorry for the lengthy answer and any inconvenience it might bring to readers of it).
Now, lets leave global variables and discuss one (among other) viable options for the communication between the two controllers in your example. From your question, I'll summarize your example as follows
UITableViewController
consisting of UITableViewCell
s, where, in these cells, you display some text, say, via instances of UILabel
.UIViewController
, accessible from the cells of VC1, containing an UITextField
instance. When user enters text into this text field, your want the text to be displayed in the associated cell in VC2 (associated in the sense that it was the cell in VC1 that was used to access VC2).We'll associate VC1 and VC2 with (cocoa touch) classes TableViewController
(TableViewController.swift) and ViewController
(ViewController.swift), respectively. The cells in the table view controller will be associated with (cocoa touch) class TableViewCell
(TableViewCell.swift). Details for these classes follow below.
For this simple example, note that we will not embed VC1 into a navigation controller (which is otherwise appropriate for table view -> view navigation).
We'll start in the storyboard, adding objects (drag-and-drop from object library) for a Table View Controller
and a View Controller
. The table view container will also, automatically, contain, in its Table View
, a TableViewCell
. Continuing in the storyboard:
UILabel
object to the TableViewCell
container in the Table View Controller
(align it as you wish)View Controller
, add a Text Field
object and a Button
object (align them as you wish).Table View Controller
.TableViewCell
to the View Controller
.Show
segue and, from the Attributes inspector, enter an identifier for it, say, ShowDetail.TableViewCell
selected, (as above; from the attribute inspector), enter an identifier for the cell. Here, we'll use simply use identifier TableViewCell.We now leave the storyboard for now and implement three classes, associated with the Table View Controller
, the View Controller
and the formers' TableViewCell
.
We start with the Table View Controller
, and implement our UITableViewController
sub-class. Note that here, instead of using an NSMutableArray
to hold the texts of the UITextLabel
in each cell, we'll simply use a String
array.
// TableViewController.swift
Import UIKit
class TableViewController: UITableViewController {
// Properties
var userTextLabels = [String]()
var numberOfCells: Int?
override func viewDidLoad() {
super.viewDidLoad()
numberOfCells = loadSampleTextLabels() // Load sample labels.
}
func loadSampleTextLabels() -> Int {
userTextLabels += ["Label #1", "Label #2", "Label #3"]
return userTextLabels.count
}
// func numberOfSectionsInTableView(tableView: UITableView) ...
// func tableView(tableView: UITableView, numberOfRowsInSection section: Int) ...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = ("TableViewCell")
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell
// Text for current cell
let cellText = userTextLabels[indexPath.row]
cell.userSuppliedTextLabel.text = cellText
return cell
}
// ... communication?
}
Where the two commented out methods are standard methods used in any UITableViewController
, for number of sections (e.g. return 1
) and cells (e.g. return (numberOfCells ?? 0)
) in the table, respectively. I'll leave fixing these to you.
Now, we associate the TableViewCell
object(s) in the table view with instances of a subclass to UITableViewCell
. Here, we'll use a very simple class for our cells; each cell just containing a single UILabel
instance (created via storyboard Ctrl-drag as an @IBOutlet
from the UILabel
in the table view cells).
// TableViewCell.swift
import UIKit
class TableViewCell: UITableViewCell {
// Properties
@IBOutlet weak var userSuppliedTextLabel: UILabel!
// Ctrl-drag from UILabel (in TableViewCell) in storyboard
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Finally, for the view controller that is accessed from the table view cells: use a single @IBOutlet
to the UITextField
used for user text input, and handle events in this text field using the pre-existing UITextFieldDelegate
. E.g.:
// ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
// Properties
@IBOutlet weak var userSuppliedText: UITextField!
// Ctrl-drag from storyboard...
var cellText: String?
override func viewDidLoad() {
super.viewDidLoad()
userSuppliedText.text = cellText ?? "..."
// Handle the user input in the text field through delegate callbacks
userSuppliedText.delegate = self
}
// UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
// User finished typing (hit return): hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
cellText = textField.text
}
}
We've also declared a string property (cellText
) here, that will as act as container for communication between VC1 and VC2.
We return to the storyboard and---from the Identity inspector---associate the three storyboard objects (Table View Controller
, View Controller
, TableViewCell
) with their associated classes that we've just written above.
We're now almost at our goal; it only remains to specify how to communicate between the two controllers.
We'll begin with communication from VC1 to VC2. In your comment above, you were on the right track (for this specific solution, anyway) by looking at the prepareForSegue(...)
method. In the class for the Table View Controller
, we add the following method:
// ... add to TableViewController.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "ShowDetail" {
let viewController = segue.destinationViewController as! ViewController
if let selectedCell = sender as? TableViewCell {
let indexPath = tableView.indexPathForCell(selectedCell)!
let currentTextInCell = userTextLabels[indexPath.row]
viewController.cellText = currentTextInCell // <-- note this
}
}
}
Hence, for VC1->VC2 communication, we can (in this example) bring whatever existing text that is currently occupying the UILabel
in the sender cell (as is specified by the String array userTextLabels
). Look at the viewDidLoad(...)
method in the ViewController.swift to see how this value is passed from VC1 and set as default text in the UITextField
in VC2.
Now, for communication VC2->VC1, which was the specific communication direction you were asking about, add another method (programmatically), again to TableViewController.swift:
// ... add to TableViewController.swift
@IBAction func unwindToTableView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? ViewController,
text = sourceViewController.cellText {
// ^ note 2nd clause of if let statement above
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update cell text
userTextLabels[selectedIndexPath.row] = text
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
}
}
Here, we define an unwind action that, when triggered, retrieves the cellText
property of the view controller that was the source of the segue, i.e., in our case, the instance of ViewController
. But how do we trigger this action?
Return to the storyboard and the View Controller
. Note the three little icons in the top of the View Controller
object, more specifically, the right-most of these, named Exit
. Ctrl-drag an action from your Button
to the Exit
icon, and select the unwindToTableView
Action Segue. When you click your button the view controller, the view unwind (exit) and land at the unwindToTableView
method in the TableViewController
.
The resulting app should look something like this:
This was way longer than I had expected, but once you get started writing... Anyway, the method above uses, naturally, no global variables, but make use of references to future (prepareForSegue
) or historic (unwindToTableView
) views to get (generally from current or historic view) or set (generally in current of future view) values by using these references (to future/historic view).
Apple has their own very thorough tutorial on an example app in the tableviewcontroller/viewcontroller context that I would recommend going over. I found it very valuable myself when I started coding Swift.
Start Developing iOS Apps (Swift)