https://i.stack.imgur.com/pw5qq.png
Not sure why it says my UITextField isn't a member. I made sure that the view controller is correctly linked to the correct Swift file.
Move it inside viewDidLoad
override func viewDidLoad () {
super.viewDidLoad()
let city = self.currentCity.text!
}
You must put this variable inside some function. But you put this inside interface of class
func whoaStringFunc() {
let city = currentCity.text!
}
Related
I am new to Swift and Xcode. I am building an Financial Expense ios app.
In my first view controller, I created a referencing outlet for a label called expenseNum.
In my second view controller, I have a function for a button called Add Expense. When it is clicked, I need it to update the expenseNum variable with the amount of the expense.
What is the best way to go about this? I had created an object of the first view controller class and accessed it like "firstviewcontroller.expenseNum" but this will create a new instance of the class and I need it to be all the same instance so it can continuously add to the same variable. Thanks for the help!
You need a delegate
protocol SendManager {
func send(str:String)
}
In first
class FirstVc:UIViewcontroller , SendManager {
func send(str:string) {
self.expenseNum.text = str
}
}
when you present SecondVc
let sec = SecondVc()
sec.delegate = self
// present
In second
class SecondVc:UIViewcontroller {
var delegate:SendManager?
#IBAction func btnClicked(_ sender:UIButton) {
delegate?.send(str:"value")
}
}
// setting delegate
in viewDidLoad of SecondVc
if let first = self.tabBarController.viewControllers[0] as? FirstVc {
self.delegate = first
}
There are several ways you can pass data from ViewController2 to another ViewController1
The best way here is Protocol Delegates
Please follow below steps to pass data
In Your SecondViewController from where you want to send data back declare a protocol at the top of class declaration
protocol SendDataBack: class {
func sendDataFromSecondVCtoFirstVC(myValue: String)
}
Now in the class , declare a object of your protocol in same ViewController
weak var myDelegateObj: SendDataBack?
And now in your Add Expense button action just call the delegate method
myDelegateObj?.sendDataFromSecondVCtoFirstVC(myValue: yourValue)
Now go to your first ViewController
the place from where you have pushed/present to SecondViewController you must have taken the object of SecondVC to push to push from first
if let secondVC = (UIStoryboard.init(name: "Main", bundle: nil)).instantiateViewController(withIdentifier: "secondVCID") as? SecondViewController {
vc?.myDelegateObj = self
self.navigationController?.pushViewController(secondVC, animated: true)
**OR**
self.present(secondVC, animated: true, completion: nil)
}
now in your FirstViewController make an extension of FirstViewVC
extension FirstViewController: SendDataBack {
func sendDataFromSecondVCtoFirstVC(myValue: String) {
}
}
I think you can make a variable in your properties in second ViewController (before viewDidLoad method)
var delegate: FirstViewController? = nil
and use from the properties of the first view controller anywhere of the second view controller.
delegate!.mainTableView.alpha=1.0
//for example access to a tableView in first view controller
The simplest way to achieve this is to use a public var. Add a new Swift file to your project, call it Globals. Declare the public variable in Globals.swift like so:
public var theValue: Int = 0
Set its required value in the first ViewController, and you'll find you can read it in the second with ease.
Relatively new to coding in Swift and am well on my way to my first App, however, this issue is totally foxing me and I cannot work out why.
Why can I not reference the InputTextField.text property?
Playground image
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
setupInputView()
}
func setupInputView () {
let InputTextField = UITextField()
InputTextField.text = "Some Text"
view.addSubview(InputTextField)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
print("InputTextField: \(InputTextField.text)")
InputTextField has been declared inside the function setupInputView(). It is only accessible inside that function. Move the declaration
let InputTextField = UITextField()
outside the function. It will then be a member of your class MyViewController. Outside of that class you can access as follows
PlaygroundPage.current.liveView.InputTextField
Go read up on scope and variable declarations before you continue
The problem with your code is, you are setting up the textField inside a function. So the textField variable has a local scope and it's become inaccessible with myViewController instance. So first of all, you have to make the textField as a global variable. Please see the code snippet below-
class MyViewController : UIViewController {
let inputTextField = UITextField()
override func loadView() {
inputTextField.text = "some text"
}
}
let myViewController = MyViewController()
PlaygroundPage.current.liveView = myViewController
print("TextFieldValue: \(myViewController.inputTextField.text!)")
Please remember one thing about the variable naming convention, it's always start with small letter and then follow camelCasing.
When I define a variable in the first tab I can then make it available in the usual way to the other tabs. But what if I define a variable in the third tab and I want it to be available in the first tab? Here is the usual set up for the variable in the first tab:
class AddCardViewController: UIViewController {
var cards = [Card]()
override func viewDidLoad() {
super.viewDidLoad()
cards = [1,2,3,4]
let barViewController = self.tabBarController?.viewControllers
let svc1 = barViewController![1] as! LearningViewController
svc1.cards = self.cards
}
in the second viewController I can then easily use the cards array
class LearningViewController: UIViewController {
var cards = [Card]()
override func viewDidLoad() {
super.viewDidLoad()
print(cards) ////works all fine
Now I want to define a variable in the second tab and make it available to the first tab. So, the other way round. I tried to use the same setup, but when I start the app it crashes because the variable is nil in the first tab - makes sense. Is there any way to do this or do I just have to define all variables in the first viewController?
I think the easiest solution is to force the second tab to load before calling the variable in question. The most brute force way to do this is to create a func in AddCardViewController that loads all the tabs in the tabBarController e.g.
func loadAllTabs() {
if let viewControllers = self.tabBarController?.viewControllers {
for viewController in viewControllers {
viewController.view
}
}
}
and call it in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
loadAllTabs()
}
I have an optional bool variable called showSettings on my first view controller which is called ViewController, and I'm popping from SecondViewController back to ViewController.
Before I pop, I want to set the bool to true. Seems wrong to instantiate another view controller since ViewController is in memory.
What's the best way to do this? I'm not using storyboards, if that's important for your answer.
Thanks for your help
So I figured it out, based mostly from this post – http://makeapppie.com/2014/09/15/swift-swift-programmatic-navigation-view-controllers-in-swift/
In SecondViewController, above the class declaration, add this code:
protocol SecondVCDelegate {
func didFinishSecondVC(controller: SecondViewController)
}
Then inside of SecondViewContoller add a class variable:
var delegate: MeditationVCDelegate! = nil
Then inside of your function that your button targets, add this:
self.navigationController?.popViewControllerAnimated(true)
delegate.didFinishSecondVC(self)
What we're doing here is doing the pop in SecondViewController, and not passing any data, but since we've defined a protocol, we're going to use that in ViewController to handle the data.
So next, in ViewController, add the protocol you defined in SecondViewController to the list of classes ViewController inherits from:
class ViewController: UIViewController, SecondVCDelegate { ... your code... }
You'll need to add the function we defined in the new protocol in order to make the compiler happy. Inside of ViewController's class, add this:
func didFinishSecondVC(controller: SecondViewController) {
self.myBoolVar = true
controller.navigationController?.popViewControllerAnimated(true)
}
In SecondViewController where we're calling didFinishSecondVC, we're calling this method inside of the ViewController class, the controller we're popping to. It's similar to if we wrote this code inside of SecondViewController but we've written it inside of ViewController and we're using a delegate to manage the messaging between the two.
Finally, in ViewController, in the function we're targeting to push to SecondViewController, add this code:
let secondVC = secondViewController()
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
That's it! You should be all set to pass code between two view controllers without using storyboards!
_ = self.navigationController?.popViewController(animated: true)
let previousViewController = self.navigationController?.viewControllers.last as! PreviousViewController
previousViewController.PropertyOrMethod
I came across this while looking for a way to do it. Since I use Storyboards more often, I found that I can get the array of controllers in the navigation stack, get the one just before the current one that's on top, check to see if it's my delegate, and if so, cast it as the delegate, set my methods, then pop myself from the stack. Although the code is in ObjC, it should be easily translatable to swift:
// we need to get the previous view controller
NSArray *array = self.navigationController.viewControllers;
if ( array.count > 1) {
UIViewController *controller = [array objectAtIndex:(array.count - 2)];
if ( [controller conformsToProtocol:#protocol(GenreSelectionDelegate)]) {
id<GenreSelectionDelegate> genreDelegate = (id<GenreSelectionDelegate>)controller;
[genreDelegate setGenre:_selectedGenre];
}
[self.navigationController popViewControllerAnimated:YES];
}
Expanding upon the answer by Abdul Baseer Khan:
For cases where the current view controller may have been loaded by different types of previous view controller, we can use the safer as? call instead of as!, which will return nil if the controller is not what we were looking for:
let previousVC = self.navigationController?.viewControllers.last as? AnExampleController
previousVC?.doSomething()
Although, you would need to repeat that for each different view controller that could load the current view controller.
So, you may want to, instead, implement a protocol to be assigned to all the possible previous view controllers:
protocol PreviousController: UIViewController {
func doSomething()
}
class AnExampleController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class AnotherController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class CurrentController: UIViewController {
// ...
func goBack() {
let previousVC = self.navigationController?.viewControllers.last as? PreviousController
previousVC?.doSomething()
}
}
I know that this has to be a simple fix, but can't seem to understand why my code is not working. Basically I am trying to send a value from a text field in 1 view to a 2nd view's label.
ViewController.swift
#IBOutlet var Text1st: UITextField
#IBAction func Goto2ndView(sender: AnyObject) {
let view2 = self.storyboard.instantiateViewControllerWithIdentifier("view2") as MyView2
//view2.Label2nd.text=text;
self.navigationController.pushViewController(view2, animated: true)
}
MyView2.swift
#IBOutlet var Label2nd: UILabel
override func viewDidLoad() {
super.viewDidLoad()
var VC = ViewController()
var string = (VC.Text1st.text) //it doesn't like this, I get a 'Can't unwrap Option.. error'
println(string)
}
-------EDITED UPDATED CODE FROM (drewag)-------
ViewController.swift
let text = "text"
var sendString = Text1st.text
println(sendString) //successfully print it out.
let view2 = self.storyboard.instantiateViewControllerWithIdentifier("view2") as MyView2
view2.Label2nd.text=sendString;
self.navigationController.pushViewController(view2, animated: true)
MyView2.swift
#IBOutlet var Label2nd: UILabel
override func viewDidLoad() {
super.viewDidLoad()
var VC = ViewController()
var string = self.Label2nd.text
println(string) //still getting the error of an unwrap optional.none
}
var VC = ViewController() creates a new instance of ViewController. Unless there is a default value, you are not going to get any value out of VC.Text1st.text. You really should use a string variable on your second view controller to pass the data to it.
Also, a note on common formatting:
Class names should start with a capital letter (as you have)
Method / function names should start with a lower case letter
UIViewController subclasses should have "Controller" included in their name, otherwise, it looks like it is a subclass of UIView which is an entirely different level of Model View Controller (the architecture of all UIKit and Cocoa frameworks)
Edit:
Here is some example code:
class ViewController1 : UIViewController {
...
func goToSecondView() {
var viewController = ViewController2()
viewController.myString = "Some String"
self.navigationController.pushViewController(viewController, animated: true)
}
}
class ViewController2 : UIViewController {
var myString : String?
func methodToUseMyString() {
if let string = self.myString {
println(string)
}
}
...
}
Note, I am not creating ViewController2 using a storyboard. I personally prefer avoiding storyboards because they don't scale well and I find editing them to be very cumbersome. You can of course change it to create the view controller out of the storyboard if you prefer.
jatoben is correct that you want to use optional binding. IBOutlets are automatically optionals so you should check the textfield to see if it is nil.
if let textField = VC.Text1st {
println(textField.text)
}
This should prevent your app from crashing, but it will not print out anything because your text field has not yet been initialized.
Edit:
If you want to have a reference to your initial ViewController inside your second you're going to have to change a few things. First add a property on your second viewcontroller that will be for the first view controller:
#IBOutlet var Label2nd: UILabel //existing code
var firstVC: ViewController? //new
Then after you create view2, set it's firstVC as the ViewController you are currently in:
let view2 = self.storyboard.instantiateViewControllerWithIdentifier("view2") as MyView2 //already in your code
view2.firstVC = self //new
Finally in your viewDidLoad in your second view controller, use firstVC instead of the ViewController you recreated. It will look something like this:
override func viewDidLoad() {
super.viewDidLoad()
if let textField = firstVC?.Text2nd {
println(textField.text)
}
}
Use optional binding to unwrap the property:
if let string = VC.Text1st.text {
println(string)
}