I am trying to perform a segue from a UITableView with news. If you push one of the news, it performs a segue to the specific news you selected.
It is easy and I have done it a few times... but I don't know what am I doing wrong this time.
The NewsDetailViewController is like this:
class NewsDetailViewController: UIViewController {
#IBOutlet weak var newsImage: UIImageView!
#IBOutlet weak var newsTitle: UILabel!
#IBOutlet weak var newsDate: UILabel!
#IBOutlet weak var newsText: UILabel!
var newsLink: String?
override func viewDidLoad() {
super.viewDidLoad()
// Hides the navigation bar.
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func closeNews(_ sender: UIButton) {
navigationController?.popViewController(animated: true)
}
}
And the segue in the NewsTableViewController is like this:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you selected the row: \(indexPath.row)")
tableView.deselectRow(at: indexPath, animated: true)
self.performSegue(withIdentifier: "goToNewsDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToNewsDetail" {
if let destination = segue.destination as? NewsDetailViewController {
destination.newsLink = "whateverlink"
destination.newsTitle.text = "whatevertitle"
}
}
}
And the line: destination.newsLink = "whateverlink"
Works perfectly.
But the line: destination.newsTitle.text = "whatevertitle"
Throws a
fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value.
And I have no idea of what if going on. The same problem happens when trying to initialise the rest of the labels in the destination.
This line is the problem
destination.newsTitle.text = "whatevertitle"
don't access outlets of the destination vc as they not yet loaded send a string and assign it to the label in the destination vc
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToNewsDetail" {
if let destination = segue.destination as? NewsDetailViewController {
destination.newsLink = "whateverlink"
destination.toSend = "whatevertitle"
}
}
}
class NewsDetailViewController: UIViewController {
#IBOutlet weak var newsTitle:UILabel!
var toSend = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.newsTitle.text = toSend
}
}
In the prepareForSegue method, the newsTitle label is still not initialised, so it is nil.
Generally, you shouldn't set the target VC's view's properties in prepareForSegue. You should declare a newsTitleText property in NewsDetailViewController:
var newsTitleText: String!
And set this property instead:
destination.newsTitleText = "whatevertitle"
Then set the newsTitle.text in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// Hides the navigation bar.
self.navigationController?.setNavigationBarHidden(true, animated: false)
newsTitle.text = newsTitleText
}
When the prepare(for:sender:) method is called, NewsDetailViewController has not loaded the view yet so you can't set the text on a label. What you want to do is create another property on NewsDetailViewController such as var newsTitleText: String?. Then in viewDidLoad you can call newsTitle.text = newsTitleText.
Related
how do you pass data from search Bar to UILabel in a different view controller
Passing Data between View Controllers
I have tried multiple questions here on stack, but it just doesn't work or the simulator ends up crashing for me
class FirstVC: UIViewController, DataEnteredDelegate {
#IBOutlet weak var label: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destination as! SecondVC
secondViewController.delegate = self
}
}
func userDidEnterInformation(info: String) {
label.text = info
}
}
// protocol used for sending data back
protocol DataEnteredDelegate: class {
func userDidEnterInformation(info: String)
}
class SecondVC: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: DataEnteredDelegate? = nil
#IBOutlet weak var searchBar: UISearchBar!
#IBAction func sendTextBackButton(sender: AnyObject) {
// call this method on whichever class implements our delegate protocol
delegate?.userDidEnterInformation(info: searchBar.text!)
// go back to the previous view controller
_ = self.navigationController?.popViewController(animated: true)
}
}
I don't find anything suspicious in your code which cause app crash. Meanwhile for data transfer between view controller we can use delegation and NotificationCenter. Below code uses NotificationCenter
let kStringTransfer = "StringTransferNotification"
class FirstViewController: UIViewController, DataEnteredDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(setString(notify:)), name: NSNotification.Name(rawValue: kStringTransfer), object: nil)
}
func getSecondVC() {
if let second = self.storyboard?.instantiateViewController(withIdentifier: "Second") as? ViewController {
self.navigationController?.pushViewController(second, animated: true)
}
}
#IBAction func searchBarBtn(_ sender: Any) {
//go to search view controller
getSecondVC()
}
#objc func setString(notify: Notification) {
//set search bar string to text field
textField.text = notify.object as? String
}
}
class SecondViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
searchBar.delegate = self
}
#IBAction func goBackBtn(_ sender: Any) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: kStringTransfer), object: searchBar.text)
self.navigationController?.popViewController(animated: true)
}
}
When I need pass just small values between 2 viewControllers, I don't use a delegate, just create a variable in second view and pass like this.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "showView") {
let vc = segue.destination as! secondViewController
vc.stringTemp = "My string"
}
}
I am new to Swift and was wondering on how to pass data from ViewController to ViewController that isn’t connected by segue. I have spend 2 hours on this and still can’t figure a way to do it.
Edit: The problem that I am confused on is How to take out all the stacked ViewController expect the root ViewController while being able to pass data back to the first ViewController from ViewController3 with
'navigationController?.popToRootViewController(animated: false)'
class ViewController: UIViewController, nameDelegate {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var textfield1: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Name"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc2 = segue.destination as! ViewController2
vc2.middleOfTransferingName = textfield1.text!
}
#IBAction func buttonPressed1(_ sender: Any) {
performSegue(withIdentifier: "go2", sender: self)
}
func nameThatWasEntered(name: String) {
label1.text = name
}
}
class ViewController2: UIViewController, nameDelegate {
var middleOfTransferingName: String = ""
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc3 = segue.destination as! ViewController3
vc3.transferedWithSegueLabelText = middleOfTransferingName
vc3.namePassBack = self
}
#IBAction func buttonPressed2(_ sender: Any) {
performSegue(withIdentifier: "go3", sender: self)
}
func nameThatWasEntered(name: String) {
}
}
protocol nameDelegate {
func nameThatWasEntered(name: String)
}
class ViewController3: UIViewController {
#IBOutlet weak var label3: UILabel!
#IBOutlet weak var textfield3: UITextField!
var transferedWithSegueLabelText: String = ""
var namePassBack: nameDelegate?
override func viewDidLoad() {
super.viewDidLoad()
label3.text = transferedWithSegueLabelText
}
#IBAction func buttonPressed3(_ sender: Any) {
namePassBack!.nameThatWasEntered(name: textfield3.text!)
navigationController?.popToRootViewController(animated: false)
}
}
I found the way to transfer ViewController3 data to ViewController!
var vc3 = ViewController3()
then making ViewController as the delegate of vc3 upon loading.
override func viewDidLoad() {
super.viewDidLoad()
vc3.namePassBack = self
}
Then going back to ViewController3, I add ""vc3."" before using the delegate.
#IBAction func buttonPressed3(_ sender: Any) {
vc3.namePassBack!.nameThatWasEntered(name: textfield3.text!)
navigationController?.popToRootViewController(animated: false)
}
if you use segue then use 'Unwind segue'
for this write given code in your 'A view controller'
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
}
In B_viewcontroller, On press button call 'Unwind segue'.
Note:- With the help of Unwind segue you can send the value for 3ed vc to 1se vc same as you send value form 1st vc to 2nd vc
for more go to this link
I am building a simple budget app, which will allow me to add specified budget and then considering my added expenses, calculate my actual outcome.
In Details Controller: 1, I want to be able to grab subtitle value from navigation bar, subtract it with value from cell's detailLabel and finally present in footer's label Wallet Balance. So far, I have been struggling with retrieving subtitle's value and doing calculation.
Here is my Main Controller: 2, which is used to send data forward to Details Controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? AddBudgetViewController {
destinationViewController.delegate = self
}
// pass cell's label to detailVC
let destinationVC = segue.destination as? BudgetDetailsViewController
let cell = sender as? BudgetCell
destinationVC?.navigationItem.setTitle(title:(cell?.budgetNameLabel.text?.capitalized)!, subtitle:(cell?.detailTextLabel?.text)!)
}
AddBudgetViewController : using delegation to pass data back to main controller
protocol BudgetDelegate: class {
func enteredBudgetData(info: String, info2: String)
}
class AddBudgetViewController: UIViewController {
weak var delegate: BudgetDelegate? = nil
#IBOutlet weak var budgetName: UITextField!
#IBOutlet weak var budgetAmount: UITextField!
//
#IBAction func saveContent(_ sender: UIButton) {
if ((budgetName.text?.isEmpty)! && (budgetAmount.text?.isEmpty)!) {
_ = self.navigationController?.popViewController(animated: true)
} else {
delegate?.enteredBudgetData(info: budgetName.text!, info2: budgetAmount.text!)
_ = self.navigationController?.popViewController(animated: true)
}
}
Using didSelectRowAt instead segue method above
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//
let budget = self.budget[indexPath.row]
let viewController = UIStoryboard(name:"BudgetList", bundle: nil).instantiateViewController(withIdentifier: "DetailsController") as! BudgetDetailsViewController
viewController.budgets = budget
navigationController?.pushViewController(viewController, animated: true)
}
DetailsController
class BudgetDetailsViewController: UITableViewController, ExpenseDelegate {
#IBOutlet weak var walletBalance: UILabel!
var expense = [Expense]()
var budgets: Budget! {
didSet {
if isViewLoaded {
calculateWalletBudget(budget:, expense:)
}
}
}
I am looking for ideas how to fix a problem I'm having with transposing data from my first view controller to the second view controller. The second view controller is being called when the user selects a table cell.
Code that populates the first tableview
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = mtgRates.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
cell.fiName.text = fetchedFiName[indexPath.row].fiName
cell.oneYear.text = fetchedFiName[indexPath.row].oneYear
cell.twoYear.text = fetchedFiName[indexPath.row].twoYear
cell.threeYear.text = fetchedFiName[indexPath.row].threeYear
cell.fourYear.text = fetchedFiName[indexPath.row].fourYear
cell.fiveYear.text = fetchedFiName[indexPath.row].fiveYear
return (cell)
}
I've watched many youtube videos but they all take a simple approach when setting up the data using an array set globally.
Code that I have been working but does nothing at this point.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
oneYearFound = self.fetchedFiName[indexPath.row].oneYear
twoYearFound = self.fetchedFiName[indexPath.row].twoYear
threeYearFound = self.fetchedFiName[indexPath.row].threeYear
fourYearFound = self.fetchedFiName[indexPath.row].fourYear
fiveYearFound = self.fetchedFiName[indexPath.row].fiveYear
performSegue(withIdentifier: "segue", sender: self)
}
I am thinking my issues is sending the fetched results to the second view controller
Thank you for any help!
More info based on the reply. You are correct I do have two view controllers on the storyboard. The code I have this far my UIViewController is
class SegueViewController: UIViewController {
#IBOutlet weak var V2TwoYear: UILabel!
#IBOutlet weak var V2FiveYear: UILabel!
#IBOutlet weak var V2FourYear: UILabel!
#IBOutlet weak var V2ThreeYear: UILabel!
#IBOutlet weak var V2OneYear: UILabel!
#IBOutlet weak var V2FiName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
V2FiName.text = foundFi[myIndex].fiName
V2TwoYear.text = foundFi[myIndex].twoYear
V2OneYear.text = foundFi[myIndex].oneYear
V2ThreeYear.text = foundFi[myIndex].threeYear
V2FourYear.text = foundFi[myIndex].fourYear
V2FiName.text = foundFi[myIndex].fiveYear
}
Why don't you pass an instance of your fetchedFiName?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedFiName = self.fetchedFiName[indexPath.row]
performSegue(withIdentifier: "segue", sender: selectedFiName)
}
Then, cast your sender as YOUR_FETCHED_FI_NAME_CLASS and pass it to your destination view controller in prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let selectedFiName = sender as? YOUR_FETCHED_FI_NAME_CLASS,
destVC = segue.destination as? SegueViewController {
destVC.passedFiName = selectedFiName
}
}
Then, in your destination viewController and after ViewDidLoad (since your labels will not be loaded before that) you may use your passedFiName to populate your labels.
override func viewDidLoad() {
super.viewDidLoad()
updateLabels()
}
func updateLabels() {
V2FiName.text = passedFiName.fiName
V2TwoYear.text = passedFiName.twoYear
V2OneYear.text = passedFiName.oneYear
V2ThreeYear.text = passedFiName.threeYear
V2FourYear.text = passedFiName.fourYear
V2FiName.text = passedFiName.fiveYear
}
Update:
I continue to have problems with this. I think I am getting closer
Controller one code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedFiName = self.fetchedFiName[indexPath.row].fiName
let selectedOneYear = self.fetchedFiName[indexPath.row].oneYear
let selectedTwoYear = self.fetchedFiName[indexPath.row].twoYear
let selectedThreeYear = self.fetchedFiName[indexPath.row].threeYear
let selectedFourYear = self.fetchedFiName[indexPath.row].fourYear
let selectedFiveYear = self.fetchedFiName[indexPath.row].fiveYear
passData = [SecondTable(passedFIName: selectedFiName, passedOneYear: selectedOneYear, passedTwoYear: selectedTwoYear, passedThreeYear: selectedThreeYear, passedFourYear: selectedFourYear, passedFiveYear: selectedFiveYear)]
performSegue(withIdentifier: "SecondViewController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let selectedFiName = sender as! ,
let destVC = segue.destination as? SecondViewController {
destVC.fiName = selectedFiName
}
}
Second View Controller Code`
struct SecondTable {
var passedFIName: String = ""
var passedOneYear: String = ""
var passedTwoYear: String = ""
var passedThreeYear: String = ""
var passedFourYear: String = ""
var passedFiveYear: String = ""
}
class SecondViewController: UIViewController {
#IBOutlet weak var fiName: UILabel!
#IBOutlet weak var sometext: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let fiDetails = SecondTable()
fiName.text = .passedFIName
sometext.text = "Some Text"
}
}
I am getting error messages at` override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
I am nit sure what to put after "Sender as "Missing value"
I have been searching for hours. One I solve this problem. my project will start to move along. Thank you for all the great help!`
I need to make a few assumptions:
You have a storyboard with a seque with the identifier "seque"
You have a view controller at the end of that seque.
You need to send oneYearFound.... all 5 of those values to the SecondViewController. Lets just say that view controller has 5 UILabels that display the five values you want to send.
Sounds like SecondViewController needs some sort of object to hold those values. Define one on that view controller, and then create a prepareForSeque method on your FirstViewController. Source
In prepareForSeque, get the destination view controller off the seque and then pass it the object.
Then use the object to populate the values for the labels in ViewDidAppear or ViewDidLoad.
Edit:
It looks like you're really close. Make sure you dont forget to include the PrepareForSeque method in the first view controller. Inside that method override you can access the second view controllers instance and set those array values.
Here is some code that should help get you thinking.
class LabelsClass {
var str1:String;
var str2:String;
init(firstName:String, secondName:String) {
str1 = firstName;
str2 = secondName;
}
}
class SegueViewController: UIViewController {
var firstLabelString:String = ""
var secondLabelString:String = ""
func setValues(labels:LabelsClass) {
self.firstLabelString = labels.str1;
self.secondLabelString = labels.str2;
}
}
class MessageListViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var first = "Game"
var second = "maniac"
var localLabelsVar = LabelsClass.init(firstName: first,secondName: second);
var destVC = segue.destination as? SegueViewController
destVC?.setValues(labels: localLobalsVar)
}
}
Sources:
How do I use prepareForSegue in swift?
How to pass prepareForSegue: an object
My code is below. When I press the 'Done' or 'Cancel' buttons, it doesn't work as I want. I did some debugging, and delegate is nil even though I set it. Please help - thanks.
class ViewController: UIViewController,EditViewControllerDelegate {
#IBOutlet weak var label: UILabel!
//页面跳转时
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditView" {
//通过seque的标识获得跳转目标
let controller = segue.destinationViewController as! EditViewController
//设置代理
controller.delegate = self
//将值传递给新页面
controller.oldInfo = label.text
}
}
func editInfo(controller:EditViewController, newInfo:String){
label.text = newInfo
//关闭编辑页面
controller.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
}
func editInfoDidCancer(controller:EditViewController){
//关闭编辑页面
controller.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
}
}
import UIKit
protocol EditViewControllerDelegate {
func editInfo(controller:EditViewController, newInfo:String)
func editInfoDidCancer(controller:EditViewController)
}
class EditViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var delegate:EditViewControllerDelegate?
var oldInfo:String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if oldInfo != nil{
textField.text = oldInfo
}
}
#IBAction func done(sender: AnyObject) {
delegate?.editInfo(self, newInfo: textField.text!)
}
#IBAction func cancel(sender: AnyObject) {
delegate?.editInfoDidCancer(self)
}
}
Try this,
class ViewController: UIViewController,EditViewControllerDelegate {
var controller: EditViewController?
#IBOutlet weak var label: UILabel!
//页面跳转时
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditView" {
//通过seque的标识获得跳转目标
controller = segue.destinationViewController as! EditViewController
//设置代理
controller.delegate = self
//将值传递给新页面
controller.oldInfo = label.text
}
}
func editInfo(controller:EditViewController, newInfo:String){
label.text = newInfo
//关闭编辑页面
controller.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
}
func editInfoDidCancer(controller:EditViewController){
//关闭编辑页面
controller.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
}
}
import UIKit
protocol EditViewControllerDelegate {
func editInfo(controller:EditViewController, newInfo:String)
func editInfoDidCancer(controller:EditViewController)
}
class EditViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var delegate:EditViewControllerDelegate?
var oldInfo:String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if oldInfo != nil{
textField.text = oldInfo
}
}
#IBAction func done(sender: AnyObject) {
delegate?.editInfo(self, newInfo: textField.text!)
}
#IBAction func cancel(sender: AnyObject) {
delegate?.editInfoDidCancer(self)
}
}
I’m not able to tell from your code how you are handling the opening of EditViewController from your ViewController.
I’m guessing that your prepareForSegue:sender: is not getting called resulting in the delegate not getting set. To fix this, you will need to add a performSegueWithIdentifier:sender: call at the point where the segue needs to happen as in
self.performSegueWithIdentifier("EditView", sender: self)
You should replace whatever code is performing the opening of EditViewController with that call.
I have an app the uses showViewController:sender: to open up a second view controller from the first one even though there is a Show segue defined in my storyboard. The segue in my storyboard doesn’t get used in that case and prepareForSegue:sender: is never called as a result.
If I replace my
showViewController(myVC, sender: self)
with
performSegueWithIdentifier("mySegue", sender: self)
then prepareForSegue:sender: will get called. If I am setting a delegate, like you are, in prepareForSegue:sender:, the delegate will get set before the segue happens.