I have two viewcontrollers, one is rootviewcontroller, the other one is selectorviewcontroller. In rootvc, there is a textfield and button, when the button is clicked, it takes us to the selectorvc where we can choose and if necessary add a new item (area) and then choose the item, after we choose it, it takes us back to the rootvc, and display the selected item in the textfield. I understand that if we don't use data persistence measures, the data added in won't persist after we recommence the app. Although I can add in new item to the selectorvc, but the newly added data just gone even after we unwind the segue back to rootvc and re-enter the selectorvc. I am not sure where I did wrong, as the data storing array is mutable. It is great if you could pointing me to the right direction. Thanks a lot.
A simple array is defined to store the data,
import UIKit
class AreaClass {
var areaName: String
init? (areaName: String) {
self.areaName = areaName
if areaName.isEmpty {
return nil
}
}
}
This is the unwind segue in the rootvc,
#IBAction func unwindWithSelectedArea(segue:UIStoryboardSegue) {
if let SelectorViewController = segue.sourceViewController as? SelectorViewController,
selectedArea = SelectorViewController.selectedArea
{
AreaSelectedTextField.text = selectedArea
}
}
This is the declaration and addnewitem in the selectorvc,
var selectedArea: String?
var selectedAreaIndex: Int?
var areas = [AreaClass]()
var newarea = AreaClass?()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
areaNewnSelectedTF.delegate = self
saveButton.enabled = false
loadSample()
// Do any additional setup after loading the view.
}
func loadSample (){
let area1 = AreaClass(areaName: "TopHill")!
let area2 = AreaClass(areaName: "Foothill")!
let area3 = AreaClass(areaName: "Summit")!
let area4 = AreaClass(areaName: "Riverside")!
areas += [area1, area2, area3, area4]
}
#IBAction func addNewArea(sender: UIBarButtonItem) {
var dupli = false
if saveButton == sender {
let areaname = areaNewnSelectedTF.text ?? ""
newarea = AreaClass(areaName: areaname)
for var index = 0; index < areas.count; ++index {
if areaname == areas[index].areaName {
dupli = true
// Mark: alert for duplicate inputs
let alert = UIAlertController(title: "Duplicate", message: "Can't have same items", preferredStyle:.Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(alert, animated: true, completion: nil)
}
}
if dupli == false {
let newIndexPath = NSIndexPath(forRow: areas.count, inSection: 0)
areas.append(newarea!)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
This is the PrepareforSegue in selectorvc
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "saveSelectionSegue" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
if let index = indexPath?.row {
selectedArea = areas[index].areaName
}
}
}
}
You need to use a delegate to pass data from a childViewController to your rootViewController.
Below there are ViewController(root) and SelectTableViewController(selector), they implement a simple example of what you look for. I used a struct to make it simple.
At first, you need to create a protocol using the role of your delegate => ViewControllerDelegate with a simple function with an argument "AreaStruct".
This protocol needs to be implemented by the "root". When you pressed the button, you performSegueWithIdentifier that will call prepareForSegue. In it, you pass yourself(ViewControllerDelegate) to the destinationViewController.
In your destinationViewController, the user select one row. By selecting, it triggers didSelectRowAtIndex. In it, you get the selectedArea (the concern AreaStruct) and with your delegate, you call choseArea(...). After that you pop the ViewController to go back to the root.
When you call choseArea, it will put the areaName into the label to display what you selected as shown by the implementation of choseArea(areaStruct : AreaStruct) in your RootViewController.(In this function, you can do whatever you want with your areaStruct)
------ EDIT ------
In your code, your "selectorviewcontroller" create the data => loadSample. So no matter what happened when you go to "selectorviewcontroller", you will always loadSample even if you added new "areas" before. I updated my example code based on your example.
ViewController -> press button -> SelectTableViewController -> add area -> select area -> back to ViewController
To summarise,
in ViewController, I setupListData when viewDidLoad is called
I pressed the button to go to SelectTableViewController
3 I passed myself(delegate) and dataList to SelectTableViewController in prepareForSegue
SelectTableViewController is loaded and display my list based on dataList !
I pressed addNewArea, it append a new area to "areas" that is only local to SelectTableViewController !
My list is refreshed and displays my new area
I select an area
I call the delegate by passing 2 arguments : What I selected and my updated areas(List); I pop SelectViewController => Popping SelectViewController, it disappear and all data are lost
When I called the delegate, I updated my label based on what I selected and also I updated dataList in ViewController with areas (from SelectTableViewController).
When I click on my button, see point 2.1. => You should understand
If you want to persist data, you could use different techniques such as singleton, core data, NSUserDefaults, etc.
ViewController.swift
import UIKit
import Foundation
struct AreaStruct {
var areaName : String
}
protocol ViewControllerDelegate : class{
func choseArea(areaStruct : AreaStruct, areas : [AreaStruct])
}
class ViewController: UIViewController, ViewControllerDelegate { //1. Implement the delegate here.
#IBOutlet weak var infoLabel: UILabel!
#IBOutlet weak var btnToVC: UIButton!
var dataList : [AreaStruct] = [AreaStruct]()
override func viewDidLoad() {
super.viewDidLoad()
self.setupListData()
}
func setupListData(){
for i in 0...5{
dataList.append(AreaStruct.init(areaName: "Coucou \(i)"))
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SelectTableViewController" {
let destinationViewController = segue.destinationViewController as! SelectTableViewController
destinationViewController.delegate = self //2. Here you passed itself to the destinationViewController so it can know how to call you !
destinationViewController.areas = dataList //
}
}
func choseArea(areaStruct : AreaStruct, areas : [AreaStruct]) {
self.infoLabel.text = areaStruct.areaName
dataList = areas
}
#IBAction func pushToSelectTableViewController(sender: AnyObject) {
//0. When pressed, you want to go to SelectTableViewController
self.performSegueWithIdentifier("SelectTableViewController", sender: nil)
}
}
SelectTableViewController.swift
import UIKit
class SelectTableViewController: UITableViewController {
var areas : [AreaStruct] = [AreaStruct]()
weak var delegate : ViewControllerDelegate? // <-- Delegate to send a mess. to ViewController
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addNewArea(sender: UIBarButtonItem) {
let hello_world = "Hello World" //For the example !
areas.append(AreaStruct.init(areaName: hello_world))
self.tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return areas.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
cell.textLabel?.text = areas[indexPath.row].areaName
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedArea = areas[indexPath.row]
self.delegate?.choseArea(selectedArea, areas: areas) //3. When you select, you pass the data to ViewController via the delegate
self.navigationController?.popViewControllerAnimated(true)//4. You dismiss itself
}
}
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I know this question has been asked so many many times. But here the scenario is changed. I am using MMParallaxView and that looks pretty awesome.
But Now at this point, I want to communicate between these two View controllers. Let me tell you that this github code helps us to make a view like ios Map App. I mean you can have Map (as 1st view controller) and you can have a list of places view controller on top of 1st View Controller. Just same like Map app.
Now I want to click on the UITableView cell and want to navigate on map. For this I know how to catch delegated method. And I am successfully getting of taps on the UItableView cell. But How to send that clicked item data to 1stView Controller so that It can show or mark selected area on Map.
I know this can also be done. But how?
To modify the example app for MMParallaxView...
In ChildBottomViewController.swift
Add this at the top (outside of the class):
protocol CellTapDelegate {
func didSelectRow(indexPath: IndexPath)
}
Add this line at the beginning of the class:
var cellTapDelegate: CellTapDelegate?
Change didSelectRowAt function to:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DispatchQueue.main.async { [weak self] in
self?.cellTapDelegate?.didSelectRow(indexPath: indexPath)
}
}
In MapViewController.swift
Change the class declaration to:
class MapViewController: UIViewController, CellTapDelegate {
and add this function:
#objc func didSelectRow(indexPath: IndexPath) {
print("Delegate got didSelectRow for: \(indexPath)")
}
Then, in SecondViewController.swift
Add this at the end of viewDidLoad():
var mapVC: MapViewController?
var bottomVC: ChildBottomViewController?
for vc in self.childViewControllers {
if vc is MapViewController {
mapVC = vc as? MapViewController
} else if vc is ChildBottomViewController {
bottomVC = vc as? ChildBottomViewController
}
}
// if we found valid child view controllers, set the delegate
if mapVC != nil && bottomVC != nil {
bottomVC?.cellTapDelegate = mapVC
}
Now, selecting a row from the "slide up from bottom" table view will send the selected indexPath to its delegate - the map view controller.
protocol CellTapDelegate {
func didTapOnItem(obj: MyObject)
}
class MyViewControllerContainingTheTableView : UIViewcontroller {
weak var delegate: CellTapDelegate?
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let item = arrayOfObjects[indexpath.row]
self.delegate.didTapOnItem(item)
}
}
//View Controller where you you will have the listner
class FirstViewController : UIViewController {
func setupParalax() {
// you have to modify this according to your need
let vc = MyViewControllerContainingTheTableView()
vc.delegate = self
}
}
extension FirstViewController: CellTapDelegate {
func didTapOnItem(obj: MyObject) {
// you have your required object here
}
}
This should be possible by implementing your own delegate, but my knowledge of MMParrallaxView is that there is only 1 view controller which displays two views meaning the view controller should be able to pass things between the two views as it is. In this case the view controller would implement the table view delegate methods and add the table view to the bottom view. Then add the map to the top view. This should allow you to catch the table view cell selection and update the map in the top view accordingly.
Example based on the structure I believe you are using:
public class FirstViewController {
public let parallaxView = MMParallaxView()
private var secondViewController: SecondViewController
private var thirdViewController: ThirdViewController
override open func viewDidLoad() {
secondViewController = SecondViewController()
thirdViewController = ThirdViewController()
super.viewDidLoad()
self.view.addSubview(parallaxView)
thirdViewController.delegate = secondViewController
parallaxView.parallaxTopView = secondViewController.view
self.addChildViewController(secondViewController)
secondViewController.didMove(toParentViewController: self)
parallaxView.parallaxBottomView = thirdViewController.view
self.addChildViewController(thirdViewController)
thirdViewController.didMove(toParentViewController: self)
}
}
public class SecondViewController: ThirdViewControllerDelegate {
public func updateMap() {
// Update map
}
}
public class ThirdViewController: UITableViewDelegate {
weak var delegate ThirdViewControllerDelegate?
private var table: UITableView = UITableView()
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.updateMap()
}
}
protocol ThirdViewControllerDelegate: class {
public func updateMap() {}
}
if the view controllers are instantiated through the Storyboard then you can try this:
public class MasterViewController {
var secondViewController: SecondViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.performSegue(withIdentifier: "MMParallaxTop", sender: nil)
self.performSegue(withIdentifier: "MMParallaxBottom", sender: nil)
}
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondVC = segue.destination as? SecondViewController {
secondViewController = secondVC
}
if let thirdVC = segue.destination as? ThirdViewController, let second = secondViewController {
thirdVC.delegate = second
}
}
}
I'm creating a quiz app with custom cells that include a label of questions and then an answer coming from a UISegmentedControl.
The values of the segmentedcontrols get changed when scrolling and this leads to an inaccurate score. I understand that this is due to UITableView reusing cells.
My tableview's datasource in my main vc is simply the labels for all my questions coming from a plist file.
The code for my custom tableviewcell class is
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var question: String = "" {
didSet {
if (question != oldValue) {
questionLabel.text = question
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
//Just for testing
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex);
}
}
where the View is stored in an .XIB file.
And the code for my main vc is
class ViewController: UIViewController, UITableViewDataSource {
let questionsTableIdentifier = "QuestionsTableIdentifier"
#IBOutlet var tableView:UITableView!
var questionsArray = [String]();
var questionsCellArray = [QuestionsTableViewCell]();
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let path = Bundle.main.path(forResource:
"Questions", ofType: "plist")
questionsArray = NSArray(contentsOfFile: path!) as! [String]
tableView.register(QuestionsTableViewCell.self,
forCellReuseIdentifier: questionsTableIdentifier)
let xib = UINib(nibName: "QuestionsTableViewCell", bundle: nil)
tableView.register(xib,
forCellReuseIdentifier: questionsTableIdentifier)
tableView.rowHeight = 108;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: questionsTableIdentifier, for: indexPath)
as! QuestionsTableViewCell
let rowData = questionsArray[indexPath.row]
cell.question = rowData
return cell
}
#IBAction func calculate(_ sender: UIButton) {
var score = 0
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
score += cell.selection.selectedSegmentIndex
}
let msg = "Score is, \(score)"
print(msg)
}
#IBAction func reset(_ sender: UIButton) {
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
cell.selection.selectedSegmentIndex = 0;
}
}
}
What I'd like to do is just keep track of all 'selection' changes of the Questions cells in an array, and then use that array in cellForRowAt. I'm just confused as to how i can dynamically keep track of changes from a view in another class. I'm new to Swift and would like to solve this is a proper MVC fashion. Thanks
Instead of a simple string array as data source create a class holding the text and the selected index
class Question {
let text : String
var answerIndex : Int
init(text : String, answerIndex : Int = 0) {
self.text = text
self.answerIndex = answerIndex
}
}
Declare questionArray as
var questions = [Question]()
Populate the array in viewDidLoad with
let url = Bundle.main.url(forResource: "Questions", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let questionsArray = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [String]
questions = questionsArray.map {Question(text: $0)}
In the custom cell add a callback and call it in the segmentChanged method passing the selected index, the property question is not needed, the label is updated in cellForRow of the controller
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var callback : ((Int) -> ())?
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex)
callback?(sender.selectedSegmentIndex)
}
}
In cellForRow add the callback and update the model in the closure
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: questionsTableIdentifier, for: indexPath) as! QuestionsTableViewCell
let question = questions[indexPath.row]
cell.questionLabel.text = question.text
cell.selection.selectedSegmentIndex = question.answerIndex
cell.callback = { index in
question.answerIndex = index
}
return cell
}
To reset the segmented controls in the cells set the property in the model to 0 and reload the table view
#IBAction func reset(_ sender: UIButton) {
questions.forEach { $0.answerIndex = 0 }
self.tableView.reloadData()
}
Now you could calculate the score directly from the model instead of the view.
Don't try to use cells to hold information. As the user scrolls through your table view, cells that scroll out of view will get recycled and their field settings will be lost. Also, newly dequeued cells will have the settings from the last time they were used.
You need to refactor your code to read/write information into a data model. Using an array of Structs as a data model is a reasonable way to go. (Or, as vadian suggests in his answer, and array of Class objects, so you get reference semantics.)
You have an IBAction segmentChanged() in your custom cell class. The next trick is to notify the view controller when the user changes the selection, and to update cells when you set them up in cellForRowAt.
I suggest defining a protocol QuestionsTableViewCellProtocol, and have the view controller conform to that protocol:
protocol QuestionsTableViewCellProtocol {
func userSelected(segmentIndex: Int, inCell cell: UITableViewCell)
}
}
Add a delegate property to your QuestionsTableViewCell class:
class QuestionsTableViewCell: UITableViewCell {
weak var delegate: QuestionsTableViewCellProtocol?
//The rest of your class goes here...
}
Update your cell's segmentChanged() method to invoke the delegate's userSelected(segmentIndex:inCell:) method.
In your view controller's cellForRowAt, set the cell's delegate to self.
func userSelected(segmentIndex: Int, inCellCell cell: UITableViewCell) {
let indexPath = tableView.indexPath(for: cell)
let row = indexPath.row
//The code below assumes that you have an array of structs, `dataModel`, that
//has a property selectedIndex that remembers which cell is selected.
//Adjust the code below to match your actual array that keeps track of your data.
dataModel[row].selectedIndex = segmentIndex
}
Then update cellforRowAt() to use the data model to set the segment index on the newly dequeued cell to the correct index.
Also update your calculate() function to look at the values in your dataModel to calculate the score, NOT the tableView.
That's a rough idea. I left some details out as "an exercise for the reader." See if you can figure out how to make that work.
i need an help, see this class
import UIKit
protocol TypesTableViewControllerDelegate: class {
func typesController(controller: TypesTableViewController, didSelectTypes types: [String])
}
class TypesTableViewController: UITableViewController {
let possibleTypesDictionary = ["bakery":"Bakery", "bar":"Bar", "cafe":"Cafe", "grocery_or_supermarket":"Supermarket", "restaurant":"Restaurant"]
var selectedTypes: [String]!
weak var delegate: TypesTableViewControllerDelegate!
var sortedKeys: [String] {
return possibleTypesDictionary.keys.sort()
}
// MARK: - Actions
#IBAction func donePressed(sender: AnyObject) {
delegate?.typesController(self, didSelectTypes: selectedTypes)
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return possibleTypesDictionary.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TypeCell", forIndexPath: indexPath)
let key = sortedKeys[indexPath.row]
let type = possibleTypesDictionary[key]!
cell.textLabel?.text = type
cell.imageView?.image = UIImage(named: key)
cell.accessoryType = (selectedTypes!).contains(key) ? .Checkmark : .None
return cell
}
// MARK: - Table view delegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let key = sortedKeys[indexPath.row]
if (selectedTypes!).contains(key) {
selectedTypes = selectedTypes.filter({$0 != key})
} else {
selectedTypes.append(key)
}
tableView.reloadData()
}
}
here the user can tap a cell of the tableView so that his prefer types are used on the next viewController for a search, now i need to build a class that do the same thing but there is no a tableview rather only 6 buttons in a view that the user can tap (so a viewController with only 6 different buttons to tap). The problem is that i don't know how to pass to the next viewController what buttons have been pressed and what are not, how can i build this class?
here is the function in the other class that need to know what buttons have been pressed
func fetchNearbyPlaces(coordinate: CLLocationCoordinate2D) {
mapView.clear()
dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
for place: GooglePlace in places {
let marker = PlaceMarker(place: place)
marker.map = self.mapView
where is "types: serchedTypes"
What you wanna do is called delegation here is how you do it:
Make a protocol like this one:
protocol TransferProtocol : class
{
func transferData(types:[String])
}
Make the view controller with the buttons conform to that protocol, I like to do it by adding extensions to my classes like so:
extension ButtonsViewController:TransferProtocol{
func transferData(types:[String]){
//Do whatever you want here
}
}
Declare a variable in your Table View Controller class with the protocol you created as its type, this is called a delegate
weak var transferDelegate:TransferProtocol?
Before you segue to the Buttons View Controller you want to set that view controller as the delegate you just created like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? ButtonsViewController
transferDelegate = vc
vc?.transferData(types: selected)
}
If done correctly you should be able to work with the array you built in the Table View Controller(TypesTableViewController)
I have created this table with 3 sections and 7 rows. The code is shown below
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var subjectTabelView: UITableView!
var slSubject = ["English Lang&Lit", "Chinese Lang&Lit", "Economics"]
var hlSubject = ["Mathematics", "Chemistry", "Biology"]
var tokSubject = ["Theory of Knowledge"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
subjectTabelView.dataSource = self
subjectTabelView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return hlSubject.count
}else if section == 1{
return slSubject.count
}else {
return tokSubject.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let subjectCell = tableView.dequeueReusableCellWithIdentifier("idSubjectCell", forIndexPath: indexPath) as! UITableViewCell
if indexPath.section == 0 {
subjectCell.textLabel?.text = hlSubject[indexPath.row]
} else if indexPath.section == 1{
subjectCell.textLabel?.text = slSubject[indexPath.row]
} else {
subjectCell.textLabel?.text = tokSubject[indexPath.row]
}
return subjectCell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "HL"
} else if section == 1{
return "SL"
} else {
return "ToK"
}
}
}
What do I have to do to make every cell in this table pushes a new view controller when it is tapped? The picture of my storyboard is shown below. In my storyboard, my view controller, I have already created a navigation controller, and made the view controller that has the table the rootViewController. And for now, my tableView has only one prototype cell and one cell identifier.
Thank you!
Suppose your "locationVC" is:
class LocationVC: UIViewController {
#IBOutlet weak var fromWhereLabel: UILabel!
//This can be changed when creating this UIViewController
var textToShow : String?
override func viewWillAppear(animated: Bool) {
if let textToShow = textToShow {
fromWhereLabel.text = textToShow
}
}
}
then, just adding function below to your code in ViewController named UIViewController (that should have a better name ;-)) you can achieve your goal.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//if such cell exists and destination controller (the one to show) exists too..
if let subjectCell = tableView.cellForRowAtIndexPath(indexPath), let destinationViewController = navigationController?.storyboard?.instantiateViewControllerWithIdentifier("locationVC") as? LocationVC{
//This is a bonus, I will be showing at destionation controller the same text of the cell from where it comes...
if let text = subjectCell.textLabel?.text {
destinationViewController.textToShow = text
} else {
destinationViewController.textToShow = "Tapped Cell's textLabel is empty"
}
//Then just push the controller into the view hierarchy
navigationController?.pushViewController(destinationViewController, animated: true)
}
}
You will be able to have a LocationVC UIViewController launched every time you tap a cell, and it will have some value to prove it right. :)
Hope it Helps!
UPDATE: Code and Instructions below are for allowing to launch
different UIViewControllers after tap on cells
1.- Let's create a class that will be the parent for every one of our new UIViewControllers (the ones we are willing to go from our tableview cell's tap):
public class CommonDataViewController: UIViewController {
//Here we are going to be putting any data we want to share with this view
var data: AnyObject?
}
2.- Let's create some sort of Navigation rules, just to be organised ;-)
enum Navigation: Int {
case vc1 = 0, vc2 = 1, vc3 = 2, vc4 = 3
//How many rules we have (for not to exceed this number)
static let definedNavigations = 4
//This must return the identifier for this view on the Storyboard
func storyboardIdentifier() -> String {
//for this example's sake, we have a common prefix for every new view controller, if it's not the case, you can use a switch(self) here
return "locationVC_\(self.rawValue + 1)"
}
}
Now, let's build upon previous code:
3.- For clarity, let's change a little our previous LocationVC (that for this example, will have an Storyboard Identifier with the text "locationVC_1")
class LocationVC: CommonDataViewController {
#IBOutlet weak var fromWhereLabel: UILabel!
//This is optional, but improves clarity..here we take our AnyObject? variable data and transforms it into the type of data this view is excepting
var thisVCReceivedData: String? {
return data as? String
}
override func viewWillAppear(animated: Bool) {
if let textToShow = thisVCReceivedData {
fromWhereLabel.text = textToShow
}
}
}
4.- Now, we trigger all of this in our didSelectRowAtIndexPath function.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Just to avoid tapping on a cell that doesn't have an UIViewController asociated
if Navigation.definedNavigations > indexPath.row {
//The view's instance on our Navigation enum to which we most go after tapping this cell
let nextView = Navigation(rawValue: indexPath.row)!
//The identifier of the destination CommonDataViewController's son in our Storyboard
let identifier = nextView.storyboardIdentifier()
//If everything exists...
if let subjectCell = tableView.cellForRowAtIndexPath(indexPath), let destinationViewController = navigationController?.storyboard?.instantiateViewControllerWithIdentifier(identifier) as? CommonDataViewController {
//here you can use a switch around "nextView" for passing different data to every View Controller..for this example, we just pass same String to everyone
if let text = subjectCell.textLabel?.text {
destinationViewController.data = text
} else {
destinationViewController.data = "Tapped Cell's textLabel is empty"
}
navigationController?.pushViewController(destinationViewController, animated: true)
}
}
}
Notice that you can achieve same results using protocols and delegate approach, this is just simpler to explain
Well to push a view controller in a UINavigationController you just use this code:
ViewController *viewController = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"locationVC"];
[self.navigationController pushViewController:viewController animated:YES];
The method you are looking for is this one:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ViewController *viewController = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"locationVC"];
[self.navigationController pushViewController:viewController animated:YES];
}
You could use prepareForSegue method. You just need to set up the destination view. or the didselectrowatindexpath
prepareForSegue code looks like :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "nameofTheSegue"
{
if let destinationVC = segue.destinationViewController as? OtherViewController{
// do whatever you want with the data you want to pass.
}
}
}
i edited my question , because set textfield maybe can't be simple, need references so this is my code, but still have issue :
this code for TableViewController :
import UIKit
protocol PaymentSelectionDelegate{
func userDidSelectPayment(title: NSString?)
}
class PopPaymentTableViewController: UITableViewController {
var delegate : PaymentSelectionDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath?) {
self.dismissViewControllerAnimated(true){
if self.delegate != nil{
if let ip = indexPath{
var payItem : PayMethod
payItem = self.myList[ip.row] as! PayMethod
var title = payItem.paymentName
self.delegate.userDidSelectPayment(title)
}
}
}
}
}
and for code TransactionViewController :
import UIKit
class TransactionViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, PaymentSelectionDelegate, UITextFieldDelegate, UIPopoverPresentationControllerDelegate{
#IBOutlet var paymentTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
func resign(){
paymentTextField.resignFirstResponder()
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if (textField == paymentTextField){
resign()
performSegueWithIdentifier("seguePayment", sender: self)
}
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
func userDidSelectPayment(title: NSString?) {
paymentTextField.text = title as! String
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguePayment"{
var VC = segue.destinationViewController as! UINavigationController
var controller = VC.popoverPresentationController
if controller != nil{
controller?.delegate = self
var paymentVC = PopPaymentTableViewController()
paymentVC.delegate = self
}
}
}
}
this issue is: variable delegate in TableViewController like seems always nil, so cant set value
NB : sorry i edited totally my question, because so many answer say cant be set textfield just like that
The context of where your TransactionViewController is, is not completely clear, but you are instantiating a new ViewController. If you want to refer to an existing ViewController, this is not the instance you are using here. If you want to create a new one and show it after your didSelectRowAtIndexPath, you have to make sure, that the TextField is instantiated in the init-Method of your ViewController. As you are not instantiating it from a Storyboard, it seems that your TransactionViewController is created programmatically. Probably you are setting the TextField only in viewDidLoad or something else after the init().
You are trying to set text to paymentTextField which is still no initialized.
For this you have to set text to paymentTextField in viewDidLoad method in TransactionViewController.
remove transVC.paymentTextField.text = title from didSelectRowAtIndexPath
Add it in TransactionViewController's viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.paymentTextField.text = self.payName
}
It is not correct way to do that. You have not initialised text field and trying to set it's text. First initialise text field:
transVC.paymentTextField = UITextField()
Then try to do something.