So I started working on a basic app and ran into an issue. The user is taken to a first view controller where they are asked to enter two team names and then press the start button (which performs a segue to the second view controller). On the second view controller are two labels (named homeIdentifier and awayIdentifier ) and I want the labels on the second view controller to update to the names the user put in on the first. I thought it would be simple, but have run into an issue; it says "fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)" It also says in red "Thread 1 EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP subcode=0x0)"
How do I do this? The start button is set to perform a segue to the second view controller already, so I don't know if something is going wrong with that process. I've tried the suggestions I've seen here but doesn't seem to work (I don't know if it's because I'm using swift 2, or because I'm transporting 2 variables, etc).
I don't think transporting two variables is the problem. Just make sure that you only perform the segue of there is two variables and one of them does not equal nil. Make sure to also create two variables in the second view controller as you will need to transfer the data over from your first view controller into those variables in the second view controller. To pass data in swift 2 I would use the prepare for segue method. You will also have to set and ID in the attributed inspector for the segue that takes you from the first view controller to the second view controller.
this goes in the first view controller .swift file
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "segueIdentifier") {
// make an instance of the second view controller
var detailVC = segue!.destinationViewController as DetailViewController;
detailVC.variable1 = "\(firstViewControllerVariable)"
detailVC.variable2 = "\(secondViewControllerVariable)"
}
}
this is what your second view controller .swift file should consist of
also make sure all your outlets are linked correctly
import UIKit
class secondViewController: UIViewController {
var variable1: String = String()
var variable2: String = String()
override func viewDidLoad() {
super.viewDidLoad()
homeIdentifier.text = variable1
awayIdentifier.text = variable2
}
}
hope this helps!
FirstVC
let sc=self.storyboard?.instantiateViewControllerWithIdentifier("SecondVC")as! SecondVC
sc.str1="Pass Text"
self.navigationController?.pushViewController(sc, animated:true)
SecondVC
class ViewDetails: UIViewController
{
var str1:String!
override func viewDidLoad()
{
super.viewDidLoad()
print(str1)
// Do any additional setup after loading the view.
}
}
Related
I have two view controllers which I am interested in passing variables from one view controller to the next in a backwards manner. To achieve this I used a protocol, however, the variable in the first view controller are not updating when going back from view controller two to view controller one:
Below is my code for the first view controller:
import UIKit
class BlueBookUniversalBeamsVC: UIViewController {
var lastSelectedTableRowByTheUser: Int = 0
var lastSelectedTableSectionByTheUser: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
print(lastSelectedTableRowByTheUser)
print(lastSelectedTableSectionByTheUser)
}
}
extension BlueBookUniversalBeamsVC: ProtocolToPassDataBackwardsFromDataSummaryVcToPreviousVc {
func dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: Int, passedSelectedTableRowNumberFromPreviousVc: Int) {
self.lastSelectedTableRowByTheUser = passedSelectedTableRowNumberFromPreviousVc
self.lastSelectedTableSectionByTheUser = passedSelectedTableSectionNumberFromPreviousVc
print("Last selected row passed back from SummaryVC is equal to \(passedSelectedTableRowNumberFromPreviousVc)")
print("Last selected section passed back from SummaryVC is equal to \(passedSelectedTableSectionNumberFromPreviousVc)")
}
Below is my code inside the second view controllerL
import UIKit
class BlueBookUniversalBeamDataSummaryVC: UIViewController {
var delegate: ProtocolToPassDataBackwardsFromDataSummaryVcToPreviousVc?
#objc func navigationBarLeftButtonPressed(sender : UIButton) {
let main = UIStoryboard(name: "Main", bundle: nil)
let previousViewControllerToGoTo = main.instantiateViewController(withIdentifier: "BlueBookUniversalBeamsVC")
if delegate != nil {
delegate?.dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: self.selectedTableSectionNumberFromPreviousViewController, passedSelectedTableRowNumberFromPreviousVc: self.selectedTableRowNumberFromPreviousViewController)
}
self.present(previousViewControllerToGoTo, animated: true, completion: nil)
}
}
The weird thing is that in Xcode console when I go back from VC2 to VC1, inside the protocol function extension in VC1 I can see the values being printed correctly. However, when the values get printed from inside viewDidLoad() both of them are showing as 0. Any idea why this is happening, is there something I am missing out here?
Your second view controller is instantiating a new instance of the first view controller rather than using the instance that was already there. The second view controller shouldn’t present the first view controller again, but rather dismiss (or pop) back to it, depending upon the first presented or pushed to it.
By the way, the delegate property of the second view controller that points back to the first one should be a weak property. You never want a child object maintaining a strong reference to a parent object. Besides, delegates are almost always weak...
I was able to perform the task of passing value from one view controller to another view controller. I had a textField and submit button in one view controller and in another I had a label, after I gave input to the textField, I was able to replace the label text with textField's text.
But now what I am trying is that when I click on submit button in first view controller , it should move to second view controller (performSegue) and at the same time it should pass the value to the third view controller..
But I am getting an error if I do that, is there a simple way to pass values from one view controller to third view controller where I can skip "n" number of view controllers which come in between the target and destination?
Here's an example:
Suppose there are three viewControllers, first viewController has a
textField and a submit button, second view Controller has a "NEXT"
button and third View Controller has a label.
When I enter the textField and click on submit , it should submit the textField.text to third View Controller and should perform segue to second view controller,
now when I am on second view controller it'll show NEXT button. After I click on next, it'll take me to third View controller where it'll show me the first View COntroller's textField's text in the label...
My question is, how to do that? I went through a lot of youtube videos of implementing segues but nobody talked about how to pass data from one view controller to another view controller by skipping some view controllers.
I hope you do understand my question.
Your time and help will be highly appreciated!
Thank You
assign storybordID to your controller for ex here i have assigned "HomeVC" (it would be greate if you assign class name and storybordid same)
let objthirdController = self.storyboard?.instantiateViewController(withIdentifier: "thirdControllerStoerybordID") as! thirdControllerclassname
objthirdController.variable = self.variable
self.navigationController?.pushViewController(objthirdController, animated: true)
The way you are able to perform the task of passing value from one view controller to another view controller.
Repeat the same when you are in next controller, till you reach your destination view controller.
you should use Notification Observer Design Pattern but its better to pass it throw the view controller instead of passing directly to destination ViewController but here is the solution:
struct NotificationKeys {
static let myKey = "myKey"
}
class SourceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func yourButtonPressed(_ sender: UIButton) {
let name = Notification.Name(rawValue: NotificationKeys.myKey)
NotificationCenter.default.post(name: name, object: nil)
performSegue(withIdentifier: "identifier", sender: self)
}
}
you can pass with object too, its so simple with object too,
just search pass data with NotificationCenter in google if you don't understand the code ;)
class DestinationViewController: UIViewController {
deinit {
NotificationCenter.default.removeObserver(self)
}
#IBOutlet weak var yourLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
createObserver()
}
func createObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(updateLabel(notification:)), name: Notification.Name(rawValue: NotificationKeys.myKey), object: nil)
}
#objc func updateLabel(notification:NSNotification) {
let isText = notification.name.rawValue == NotificationKeys.myKey
let text = isText ? "\(isText)" : ""
yourLabel?.text = text
}
}
I have Table View Controller, intended to serve as a settings page, that contains a UISegmentedControl with 3 segments:
class SettingsView: UITableViewController {
#IBOutlet weak var ButtonSelection: UISegmentedControl!
//Index 0 is default selection (first)
override func viewDidLoad() {
super.viewDidLoad()
// some code
}
Separately, I have a Navigation View Controller that controls 3 different UIViewControllers with corresponding Storyboard IDs ("first", "second", and "third").
I'm trying to prepare the Navigation View Controller to present the appropriate View Controller based on UISegmentedControl selection. However, I keep getting "fatal error: unexpectedly found nil while unwrapping an Optional value" and not sure how to resolve.
This is what I have tried:
class NavViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if SettingsView().ButtonSelection.selectedSegmentIndex == 0 {
print ("first segment is selected")
let destinationController = storyboard?.instantiateViewController(withIdentifier: "first")
else if SettingsView().ButtonSelection.selectedSegmentIndex == 1 {
print ("second segment is selected")
let destinationController = storyboard?.instantiateViewController(withIdentifier: "second")
else if SettingsView().ButtonSelection.selectedSegmentIndex == 0 {
print ("third segment is selected")
let destinationController = storyboard?.instantiateViewController(withIdentifier: "third")
Could anyone point me in the right direction, please? Thanks in advance!
You have a couple of problems.
Problem 1: A UITableViewController is not set up to host anything but a table view. It's content view is locked to be a table view. If you want a table view that's managed by a UITableViewController and you want other content in the view controller, you need to make the UITableViewController a child of another view controller. The good news is that that is trivially easy using a container view and an embed segue.
Problem 2 is a "don't do that" problem. You should treat a view controller's views as private. Another view controller should not try to look at or change another view controller's segmented control. (It's bad design, and it also can lead to crashes like the one you describe because you can't be sure if the other view controller's views have been loaded yet.) Instead, you should add an integer property "selectedIndex" to the view controller that contains the segmented control, and use that to read/write the selected segment. (In OOP terms, you add a public interface to your view controller's "contract" that exposes the features you want to expose, and then add code that provides that interface.)
I have four ViewController, I don't use an UITabbedbar because It's more difficult to customize.
I use modal segue but I think the memory consumption is excessive.
this is a screen shot of my first and second VC.
What I have to use to change View correctly?
That's the code I use :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "second") {
let secondVC = segue.destinationViewController as SecondViewController;
}
}
From your Storyboard diagram, it is clear that you have created a segue from each button in your "tab bar" to another view controller. Except for the unwind segue, segues always create a new instance of the view controller they are switching to. So if you use your setup to switch from view controller 1 to view controller 2 and then back to view controller 1, you won't be returning to the view controller you came from but instead you will be creating an entirely new view controller 1.
This is why your memory consumption is excessive. You keep creating view controllers until your app crashes.
I would recommend you return to using a tab bar controller. They were designed to allocate the view controllers once up front and then just switch between them. Also, they have a standard look for a reason, it helps the user of your app know immediately how to interact with them.
To pass data between tabs, you won't use segues because there is no segue happening when you switch tabs. There are many ways you can do this, but they all boil down to having model data stored where all of the tabs can access it. This can be done with CoreData in a larger app. For a simple app, you can do the following:
Create a custom subclass of UITabBarController. Let's call it CustomTabBarController. Have that class create and hold the model data that will be accessed by each of your tabs.
CustomTabBarController.swift:
import UIKit
// This class holds the data for my model.
class ModelData {
var name = "Fred"
var age = 50
}
class CustomTabBarController: UITabBarController {
// Instantiate the one copy of the model data that will be accessed
// by all of the tabs.
var model = ModelData()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
In your Storyboard, in the Identity Inspector, change the class of UITabBarController to CustomTabBarController.
In viewWillAppear in each of your tabs, get a reference to the model data and then you can use it.
FirstViewController.swift:
import UIKit
class FirstViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Get a reference to the model data from the custom tab bar controller.
let model = (self.tabBarController as! CustomTabBarController).model
// Show that we can access and update the model data from the first tab.
// Let's just increase the age each time this tab appears and assign
// a random name.
model.age += 1
let names = ["Larry", "Curly", "Moe"]
model.name = names[Int(arc4random_uniform(UInt32(names.count)))]
}
}
SecondViewController.swift:
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var ageLabel: UILabel!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Get a reference to the model data from the custom tab bar controller.
let model = (self.tabBarController as! CustomTabBarController).model
// This tab will simply access the data and display it when the view
// appears.
nameLabel.text = model.name
ageLabel.text = "\(model.age)"
}
}
I need to update a variable in a TableViewController and I can't find the way to do it, can someone explain me why this is not working please?
I'm getting mad.
From my view controller this is the code I'm running:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let TV = storyboard.instantiateViewControllerWithIdentifier("tbController") as myTableViewController
TV.x = "test"
And then, from the TableViewController class:
class myTableViewController: UITableViewController {
var x:String!
override func viewDidLoad() {
super.viewDidLoad()
println("Value of x is: \(self.x)")
}
}
And the printed value is: nil
Why? What is wrong with that? I don't understand :-(
Updated Picture
First, give the segue between the ViewController and the TableViewController an identifier (Example: "TableViewSegue"
Then in the ViewController, use prepareForSegue to pass data from ViewController to TableViewController
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 == "TableViewSegue" {
let vc = segue.destinationViewController as myTableViewController
vc.x = "Test"
}
}
There could be several issues here.
1. You should embed the tableview into the initial viewcontroller and create it as an IBOutlet
Edit: from the updated picture it appears that you want to click the top right button and go to the tableview. Therefore, this is an incorrect statement.
You also need to make your Viewcontroller (either the tableviewcontroller or the main viewcontroller if you chose to follow #1 above) a UITableViewDelegate and UITableViewDataSource
If you are expecting to change the label listed on the image shown, you will need to use label.text = "String" assignment to change what is displayed there
You have not set an initial variable for x inside the tableviewcontroller.
Also, as a point, your order of operations isn't properly set, so it will always display nil. Because if you look at how you built this:
You have a println inside of a viewdidload on the tableview. This variable you are printing has NOT been set yet, so it is nil
You then created an instance of this class. As soon as you created that instance, the viewdidload method fired and it printed a nil line.
THEN you changed the variable via the TV.x method. But there is no println check there so you're not able to see what you did.