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.
Related
I'm learning Programmatic UI and am a little bit obsessed with clean code.
I'm currently building a TabBarVC so that I can manage all of my VC's but I get an error message while doing this.
import UIKit
class MainTabBarVC: UITabBarController {
let firstVC = FirstVC()
let secondVC = SecondVC()
let firstNavVC = UINavigationController(rootViewController: firstVC) // Cannot use instance member 'firstVC' within property initializer; property initializers run before 'self' is available
let secondNavVC = UINavigationController(rootViewController: secondVC) // Cannot use instance member 'secondVC' within property initializer; property initializers run before 'self' is available
let viewControllers = [firstNavVC, secondNavVC]
override func viewDidLoad() {
super.viewDidLoad()
self.setupViews()
}
func setupViews() {
// Nav Configs
self.firstVC.view.backgroundColor = .systemBackground
self.firstVC.navigationItem.title = "First Nav"
self.secondVC.view.backgroundColor = .systemBackground
self.secondVC.navigationItem.title = "Second Nav"
// Tab Configs
self.firstNavVC.tabBarItem.title = "First TAB"
self.secondNavVC.tabBarItem.title = "Second TAB"
}
}
I know if I put firtNavVC, secondNavVC, and viewcontrollers inside the setupViews it is gonna work but I don't like it when one function has too many lines of codes especially when it gets bigger.
So except for my question, are there any extension or enum functions that I can easily manage all of my UINavigationController, UITabBarController, and UIViewController such as enumeration that can call the function whenever I need to call or add a new VC.
You could change your lets into lazy vars.
class MainTabBarVC: UITabBarController {
lazy var firstVC = FirstVC()
lazy var secondVC = SecondVC()
lazy var firstNavVC = UINavigationController(rootViewController: firstVC)
lazy var secondNavVC = UINavigationController(rootViewController: secondVC)
lazy var viewControllers = [firstNavVC, secondNavVC]
override func viewDidLoad() {
super.viewDidLoad()
self.setupViews()
}
However, I think your impulse to maintain instance property references to all these view controllers is mistaken. What I would do is just move the lets into setupViews. You don't need a permanent reference to any of these objects; you just need to create them, configure them, and assign the view controllers as children of your tab bar controller (your code is missing that crucial step, by the way).
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!
}
I have the following code in one of my classes.
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
parkCode = tableView.cellForRowAtIndexPath(indexPath).text
RTATab.codeText.text = parkCode.substringToIndex(3)
RTATab.codeLetter.text = parkCode.substringFromIndex(3)
self.dismissModalViewControllerAnimated(true)
}
The RTATab referenced above is another class I have made (type UIViewController) and in that class I have declared it as a global class as show below as I need to access some of the textfields (codeText and codeLetter) in its view.
import UIKit
import messageUI
import CoreData
import QuartzCore
var RTATab : ViewController = ViewController()
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
//some code
}
When I run this, I get a can't unwrap optional.none error on the line RTATab.codeText.text = parkCode.substringToIndex(3).
Can someone please help. Do I need to have an initialiser in viewController class?
Thanks
You are getting this error the text of the cell is nil. Before calling methods on parkCode, you must first check if it is nil:
let possibleParkCode = tableView.cellForRowAtIndexPath(indexPath).text
if let parkCode = possibleParkCode {
RTATab.codeText.text = parkCode.substringToIndex(3)
RTATab.codeLetter.text = parkCode.substringFromIndex(3)
}
You're getting the error because RTATab.codeText and RTATab.codeLetter are nil -- the way you're initializing RTATab doesn't actually link up its properties with the text fields in your storyboard. If you truly just need a global version of the view controller, you'd need to give it a storyboard ID and load it using something like:
var RTATab: ViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("RTA") as ViewController
However, my guess is that it's a view controller you've already displayed elsewhere that you want to update, in which case you're better off just setting up a data structure that can hold the "parkCode" values and pulling them back into the correct view controller when it's time to display them.
Either parkCode or RTATab.codeText don't exist (are nil). You need to check for their existence prior to dereferencing either of them.
override func tableView (tableView: ...) {
if let theParkCode = tableView.cellForRowAtIndexPath(indexPath).text {
parkCode = theParkCode;
if let theCodeText = RTATab.codeText {
theCodeText.text = parkCode?.substringToIndex(3)
}
if let theCodeLetter ... {
// ...
}
}
}
Note: the above code depends on how your ViewController (the class of RTATab) declares its instance variables for codeText and codeLetter - I've assumed as optionals.
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)
}
I have a calculator class, a first ViewController to insert the values and a second ViewController to show the result of the calculation. Unfortunately I get a error called "Can't unwrap Optional.None" if I click the button. I know it's something wrong with the syntax, but I don't know how to improve it.
The button in the first Viewcontroller is set to "Segue: Show (e.g. Push)" in the storyboard to switch to the secondViewController if he gets tapped.
the calculator class is something like:
class Calculator: NSObject {
func calculate (a:Int,b:Int) -> (Int) {
var result = a * b
return (result)
}
}
The Viewcontroller calls the function, inserts a/b and wants to change the label which is located in the secondviewcontroller:
class ViewController: UIViewController {
#IBAction func myButtonPressed(sender : AnyObject) {
showResult()
}
var numberOne = 4
var numberTwo = 7
var myCalc = Calculator()
func showResult () {
var myResult = myCalc.calculate(numberOne, b: numberTwo)
println("myResult is \(String(myResult))")
var myVC = secondViewController()
myVC.setResultLabel(myResult)
}
And here is the code of the secondViewController
class secondViewController: UIViewController {
#IBOutlet var myResultLabel : UILabel = nil
func setResultLabel (resultValue:Int) {
myResultLabel.text = String(resultValue)
}
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
In Swift, everything is public by default.
Define your variables outside the classes:
import UIKit
var placesArray: NSMutableArray!
class FirstViewController: UIViewController {
//
..
//
}
and then access it
import UIKit
class TableViewController: UITableViewController {
//
placesArray = [1, 2, 3]
//
}
The problem here is that the FirstViewController has no reference to the instance of SecondViewController. Because of this, this line:
secondViewController.setResultLabel(myResult)
does nothing (except probably causing the Can't unwrap Optional.None error). There are a few ways to solve this problem. If you are using storyboard segues you can use the -prepareForSegue method of UIViewController. Here is an example:
In FirstViewController:
override func prepareForSegue(segue: UIStoryboardSegue!,sender: AnyObject!){
//make sure that the segue is going to secondViewController
if segue.destinationViewController is secondViewController{
// now set a var that points to that new viewcontroller so you can call the method correctly
let nextController = (segue.destinationViewController as! secondViewController)
nextController.setResultLabel((String(myResult)))
}
}
Note: this code will not run as is because the function has no access to the result variable. you'll have to figure that out yourself :)
I think the issue here is, you are trying to set the UI component (here, its the label : myResultLabel)
When segue is fired from first view controller, the second view has not yet been initialized. In other words, the UI object "myResultLabel" is still nil.
To solve this, you will need to create a local string variable in second controller. Now, set that string to what you are trying to display, and finally, set the actual label in "viewDidLoad()" of the second controller.
Best Regards,
Gopal Nair.