I am getting nil value error when I try to change UIlabel from a different swift file other than ViewController.
//ViewController.swift
import UIKit
class MainViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
myLabel?.text = "foo"
}
func label(stringVal: String? = nil){
myLabel?.text = stringVal!
}
}
//Source.swift
//Do working here and get a string value from struct
// I saved that string in stringValue
let VC = MainViewController()
VC.label(stringVal: stringValue!)
Please let me know what am I doing wrong.
View controller controls are not created in constructor, it is expected to be null. You need to show that view controller before to run label method and avoid the error.
A valid option would be to set an internal variable in the constructor and in viewDidLoad, when the label is already created, you could do
myLabel.text = stringValue // stringValue is comes from constructor.
Related
I am recreating a simple app that is a paramedic case logger, effectively it is a bunch of buttons or textfields that get their .text value time stamped and pasted into a UItextView field to create a chronological list of action to refer back to when a paramedic writes up their patient notes.
I have created a tab bar controlled app using the Xcode template.
I have created the UIbuttons and UItextfields that create and paste the string into a test UItextview on the same field.
I have created a func that creates the time stamp string
I run into trouble getting the same string to show on the UItextview on the 2nd view.
RESEARCH
As I am only new to this I have read lots of posts on here with user defaults, segues, delegates/protocols and cannot seem to effectively implement these in my specific circumstance (still not super sure I understand those enough yet to do it).
I have got the point that I have saved the string into a user.default value and then I can use that value in the 2nd view with a func to insert the text and clear the user.default value but I cannot seem to get the function to run from the first view? (get the encounter nil value error).
I feel like this should be a pretty simple process, so not getting it to function is very frustrating...
QUESTION
So I was hoping someone might be able to demo the simple way to take a UItextField.text input from FirstViewController and then when a UIbutton is pressed on FirstViewController pass the string to SecondViewController and insert it into a UITextView field (I don't want to display the 2nd view).
From this demo I'm hoping I can reverse engineer a solution into my code as a learning opportunity without just having my exact code handed to me on a silver platter.
import UIKit
class FirstViewController: UIViewController, UITextFieldDelegate {
// View OUTLETS
#IBOutlet weak var customNoteField: UITextField!
#IBOutlet weak var reportTextViewFieldTemp: UITextView!
#IBOutlet weak var lastEntryTV: UITextView!
#IBOutlet weak var caseIDTF: UITextField!
// Class VARIABLES
let timeButtonArray = ["blank0", "Call Received1", "Dispatched2", "Enroute3", "On Scene4", "At Patient5", "Depart Scene6", "At Hospital7", "Available8",]
// VIEW DID LOAD STUFF
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
self.customNoteField.delegate = self
}
//TIMING BUTTON PRESSED
#IBAction func timeButtonPressed(_ sender: UIButton) {
//GATHER DATA
let timingButtonInput = timeButtonArray[sender.tag]
let DTGstamp = dateTimeStamp()
//FORM STRING
let buttonData = ("\(DTGstamp) \(timingButtonInput) \n")
//SEND buttonDATA TO ReportViewController
/*??? The part I need help with*/
}
//CUSTOM ENTRY UITextfield PRESSED
#IBAction func customNoteDone(_ sender: Any) {
//GATHER DATA
let DTGstamp = dateTimeStamp()
let customNote = customNoteField.text
//FORM STRING
let customData = ("\(DTGstamp) \(customNote) \n")
//TEST FOR EMPTY TEXT
if customNote != "" {
//SEND customDATA TO ReportViewController
/*??? The part I need help with*/
}
//FUNCTION TO GENERATE DATE,TIME GROUP AS CONSTANT
func dateTimeStamp() -> String {
var date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "ddLLLyy HH:mm:ss"
print (dateFormatter.string(from: date))
return (dateFormatter.string(from: date))
}
}
//
// ReportViewController.swift
// EMS Case Logger
//
// Created by Allan Ravenscroft on 3/3/19.
// Copyright © 2019 Allan Ravenscroft. All rights reserved.
//
import UIKit
class ReportViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var reportTextViewField: UITextView!
#IBOutlet weak var reportEmailButton: UIButton!
#IBOutlet weak var reportEditButton: UIButton!
#IBOutlet weak var reportClearButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
hideKeyboardWhenTappedAround()
reportEditButton.layer.cornerRadius = reportEditButton.frame.height / 2
reportEmailButton.layer.cornerRadius = reportEmailButton.frame.height / 2
reportClearButton.layer.cornerRadius = reportClearButton.frame.height / 2
}
//Actions when the clear button is pressed
#IBAction func clearButtonPressed(_ sender: Any) {
reportTextViewField.text = ""
}
//Actions when the edit button is pressed
#IBAction func editButtonPressed(_ sender: Any) {
}
//Actions when the email button is pressed
#IBAction func emailButtonPressed(_ sender: Any) {
showMailComposer(bodytext: reportTextViewField.text)
}
}
I know this is not a demo, but maybe can help you
Make a new NSObject file
inside the class put a new variable
import UIKit
class test: NSObject {
var a:String = ""
}
at your ViewController declare a new var referencing the class
let testVar = test()
in your customNote checker
test.a = customNote
then at your 2nd ViewController, do the same thing
let testVar = test()
testVar.a //this is your customNote String
hope this helps
When using the storyboard, you can draw 2 buttons onto it, and add then add them to an array by creating:
#IBOutlet private var cardButtons: [UIButton]!
This outlet collection is now an array of both buttons that loads before viewDidLoad as a variable.
How do I create this array of buttons programmatically without using storyboard?
I've tried this but it gives me a declaration error:
class ViewController: UIViewController {
var cardButtons = [CardView]()
let cardOne = CardView()
cardButtons.append(cardOne)
...rest of viewController standard class
}
There a several points of time in the ViewController's lifecycle where you might want to fill the cardButtons array. For example you could do this in viewDidLoad.
class ViewController: UIViewController {
var cardButtons = [CardView]()
override func viewDidLoad() {
super.viewDidLoad()
let cardOne = CardView()
cardButtons.append(cardOne)
}
}
Keep in mind that viewDidLoad is called every time the View is loaded into memory. In my example cardOne would be recreated every time. To avoid this you could store cardOne in a instance var, as you did initially.
class ViewController: UIViewController {
var cardButtons = [CardView]()
let cardOne = CardView()
override func viewDidLoad() {
super.viewDidLoad()
cardButtons.append(cardOne)
}
}
As I said, there several points of time in the ViewController's lifecycle where you might want to fill the cardButtons array. Other functions could be:
viewDidAppear(), to fill the area every time the view appears.
init(), if you are not using storyboard at all.
Here is what I ended up doing:
class ViewController: UIViewController {
let uiView: UI = {
var ui:UI = UI()
let cardOne = CardView()
let cardTwo = CardView()
ui.addButton(item: cardOne)
ui.addButton(item: cardTwo)
return ui
}()
...rest of viewController standard class
}
class UI {
var cardButtons = [CardView]()
func addButton(button: CardView){
cardButtons.append(button)
}
}
Is it possible to have an #IB Action function inside of viewDidLoad() ?
The action is a simple one - a Stepper that increases other label.text values accordingly. However, the values that the stepper needs to work with depend on the return content of a url - which are only known after the viewDidLoad() of course.
So I think I can't have the IBaction way up on top before the viewDidLoad(), and the error I get if I try to do my IB action inside of the viewDidLoad() is:
"Only instance methods can be declared ‘IBAction' ”
EDIT
Let me clarify myself, sorry for the confusion. I know I need an outlet to get the UIStepper values from. I have that:
#IBOutlet weak var stepper: UIStepper!
I then have an action also connected to same UIStepper that will increase/decrease value of a label's text (new_total) accordingly:
#IBOutlet weak var new_total: UILabel!
#IBAction func step_up_pass(sender: AnyObject) {
new_total.text = "\(Int(stepper.value))"
}
However, I want to start out with a value (todays_price) I'm getting back from a json request and use that as a starting point, to multiply it using the stepper and put the multiplied value into the label's text.
I have a struct in a separate file that defines my object so:
struct PassengerFromOtherBus {
var fname: String?
var lname: String?
var todays_price: Int?
init(json: NSDictionary) {
self.fname = json["fname"] as? String
self.lname = json["lname"] as? String
self.todays_price = json["todays_price"] as? Int
}
}
So later on in the view controller, inside of the viewDidLoad(), after connecting to the URL and then parsing it using NSJSONSerialization and a bunch of other code here (that I don't need to confuse you with) I finally have my value todays_price. So my question is, how do I get my action to use that value when it's only known inside of my viewDidLoad()? Xcode will not even let me connect the IBAction to anywhere inside the viewDidLoad function!
This is not done with an Action but with an Outlet. Connect the Stepper from IB as an Outlet to your ViewController. Then just set the values of the Stepper in ViewDidLoad.
I would never go directly from a UIStepper.value to UILabel.text.
Use an intermediary variable to store the value.
Do the same for the return from the JSON. By setting a didSet function on those variables you can update the UI when any of the values is updated.
class FirstViewController: UIViewController {
var todays_price: Int = 0 {
didSet { // didSet to trigger UI update
myLabel.text = "\(stepperValue * todays_price)"
}
}
var stepperValue : Int = 1 {
didSet { // didSet to trigger UI update
myLabel.text = "\(stepperValue * todays_price)"
}
}
#IBOutlet weak var myStepper: UIStepper!
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
//
let returnValueFromJson = 10
todays_price = returnValueFromJson
}
#IBAction func stepperUpdate(sender: AnyObject) {
stepperValue = Int(myStepper.value)
}
}
Just add a variable to the top of your view controller to hold the value from your json request. Then in viewDidLoad you update that variable, and then you can use it to set your label and inside the IBAction (that doesn't have to be inside viewDidLoad).
So you would do something like this:
class WhateverViewController: UIViewController {
var todays_price: Int!
override func viewDidLoad() {
super.viewDidLoad()
todays_price = // The value you got from json goes here
new_total.text = "\(todays_price)"
}
#IBAction func step_up_pass(sender: AnyObject) {
new_total.text = "\(Int(stepper.value) * todays_price)"
}
}
this is my prepare for segue code
switch segue.identifier! {
case SegueIdentifiers.SecondUIViewController.rawValue:
print("\(SegueIdentifiers.SecondUIViewController.rawValue)")
let secondViewController = segue.destinationViewController as? SecondViewController
secondViewController!.resultTextField.text = "asdfasdf"
//break
default:
print("nothing sweetheart")
break
}
i got nil exception on this line
secondViewController!.resultTextField.text = "asdfasdf"
why ? the text field already there
this is the second view controller
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var resultTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Your outlets are not set until viewDidLoad. Before that they are all nil. This is why they are optionals. Thus when you try to access them from prepareForSegue they are still nil.
I recommend you add a new property to your view controller, and set that from prepareForSegue. Later when your outlets are set you can update your text field.
// in prepareForSegue
let secondViewController = segue.destinationViewController as? SecondViewController
secondViewController!.resultText = "asdfasdf"
And then in your view controller
class SecondViewController: UIViewController {
var resultText: String = "" {
didSet {
resultTextField?.text = text
}
}
#IBOutlet weak var resultTextField: UITextField! {
// Will be set once in viewDidLoad.
// Whenever that happens update text.
didSet {
resultTextField.text = resultText
}
}
}
Now you can modify your text field from the resultText property even before the outlets are set, and they will be updated when set thanks to the didSet property observer.
In my application I have a textbox that should be filled with a Double and the number should be saved into a variable but there's an error.
I dragged and dropped the textbox into ViewController.swift so it should be linked. I created a #IBOutlet. I called the textbox mmolText and the variable mmol.
I tried something like: var mmol = mmolText.text but it shows an error:
'ViewController.Type' does not have a member named 'mmolText'.
What's the problem? How can I solve it? Besides the type of the content of the textbox is a string but I should convert it into Double.
Here the code of ViewController.swift is:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mmolText: UITextField!
var mmol = mmolText.text
#IBOutlet weak var mmolLabel: UILabel!
#IBOutlet weak var mgLabel: UILabel!
#IBAction func convertBM(sender: AnyObject) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
It seems like we probably simply want mmol to exist as a convenient way for getting the text property out of the mmolText textfield, right? So why not use a computed property:
var mmol: String {
get {
return mmolText.text ?? ""
}
set {
mmolText.text = newValue
}
}
The get makes use of the nil coalescing operator. UITextField's text property hasn't been updated with the Objective-C nullability annotations yet, so we need to handle the case of it potentially returning nil.
If we want this to be readonly, we can simply omit the set part.
If we want this as a Double, we can modify the above computed property to look more like this:
var mmol: Double {
get {
return ((mmolText.text ?? "0") as NSString).doubleValue
}
set {
mmolText.text = String("%f", newValue)
}
}
And again, if we want this to be readonly, we can simply omit the set half. And of course, the format string can be played around with to get the string version of the double to show up exactly as you intend when using this set method.
class ViewController: UIViewController {
#IBOutlet weak var mmolText: UITextField!
var mmol: String!
override func viewDidLoad() {
super.viewDidLoad()
mmol = mmolText.text
}
}
This way it works. I can remember something like because at that stage, the properties can exist. Which means, it can be there or it isn't. That's why you can't do it like that.
Don't pin me on this explanation though, I'm not very sure.
mmolText is a property on self. You can't refer to it there because self has not been initialized yet.
You'll have to assign it within awakeFromNib, viewDidLoad, etc.