Populating textField from global variable fails - swift

I am fairly new to Swift and I am using Adafruit's Basic Chat iOS app https://github.com/adafruit/Basic-Chat that uses BLE and I am adding my own viewController and pushing that instead of the one Adafruit provides. My ViewController (ServoViewController) appears as expected.
I am communicating with an Arduino over BLE and receiving data from the Arduino and am parsing the relevant part (and printing to terminal) the data in the BLECentralViewController file.
I have set up a String variable outside of the BLECentralViewController class (so from my understanding it should be global). I read data into that variable in the BLECentralViewController and again print it successfully.
I then try to use that String variable to populate a textField in my viewController, however the textField never gets written to.
In the BLECentralViewController file I have the variables declared outside of the class like this:
var dataReceived: String = ""
public var batteryVal: String = "" // global variable
I check the incoming data from the Arduino. If it contains the String "Battery" I know it is the battery data. I can receive and get the substring (the battery %) and print it to the terminal.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic == rxCharacteristic {
if let ASCIIstring = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue) {
characteristicASCIIValue = ASCIIstring
print("Value Received: \((characteristicASCIIValue as String))")
dataReceived = (characteristicASCIIValue as String)
if dataReceived.contains("Battery:") {
print ("Battery value: ", dataReceived)
print("Substring: ", dataReceived.substring(from: 8))
batteryVal = dataReceived.substring(from: 8)
print("Printing batteryVal: ", batteryVal)
}
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Notify"), object: nil)
cvcrd.reportValvePosition()
}
}
}
In my viewController (ServoViewController) I have the following function
func reportBatteryPercent() {
// print battery value here
batteryField.text = batteryVal
print("Reporting Battery Percentage here...", batteryVal)
}
To start with I just wanted to populate the textField when ServoViewController first loads, so I called reportBatteryPercent() in viewDidLoad().
In the terminal output window I see that "Reporting Battery Percentage here..." appears, but there is no value printed in the terminal output for batteryVal and batteryField.text is empty.
Since I am pushing my ServoViewController there is no segue, so I figured a global variable would be an easy way to access that data.
What am I doing wrong with accessing the global variable from my viewController?
EDIT: As per recommendations I have put the reportBatteryPercent() in viewWillAppear like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
reportBatteryPercent()
}
However the data still doesn't print when in the ServoViewController.
I tried changing the declaration of the String from:
public var batteryVal: String = ""
to:
public var batteryVal: String = "999"
and now it prints 999 in the textField. So the problem now seems to be ServoViewController reading the batteryVal string after it has been updated by the BLECentralViewController
EDIT 2: I have confirmed the above my going back to the first viewController (BLECentralViewController) and then returning to the second viewController (ServoViewController). The correct value is now displayed.
I am away from my Apple machine now, but perhaps I could fix this by when I go to push the second ViewController, I could see if the value of interest is an empty string and sleep for a second?
Would this sleep the main thread?
If so how would I sleep the thread that is doing the push of the viewController?

Try to call reportBatteryPercent in viewWillAppear. And you will get what you want.
Here may be some problems: global variables are empty before viewDidLoad, or just your controller is not prepared to handle this vars.
To check it, set starting values of global vars to non-empty strings.

Since I am pushing my ServoViewController there is no segue, so I figured a global variable would be an easy way to access that data
No you still can send value to the next vc like
let vc = stroybard.....
vc.someProperty = // value
self.navigationController?.push
plus you're discouraged from using global vars as they're confusing
didUpdateValueFor is an asynchnous method so you need to make sure that the varaible you declare as global has a correct value when you push

Global variables are lazy, so you cannot depend on them. You should either have the variable as a static in a struct or in a class with a singleton reference.
See How to create a global variable? for more info.

Related

Text label not updating on screen, even though it shows up on `print()`

I'm writing an application which has an NSSplitViewController as the main View-Controller. I have it linked-up so that clicking a button in the menubar will trigger an #IBAction which then calls a function in one of the sub-View-Controllers.
if let board = storyboard {
let imageController = board.instantiateController(withIdentifier: "VC_image_ID") as! VC_image
imageController.viewDidAppear() // necessary or else I'll get an "Unexpectedly found nil" later on
DispatchQueue.global().async{imageController.processImage(path)} // path variable was set earlier in the code, but not shown here
}
Inside the sub-View-Controller (named VC_image), I'm trying to change the stringValue of a label by using the following code:
public func processImage(_ path: String) {
DispatchQueue.main.async {
self.imageText.stringValue = path
print(self.imageText.stringValue)
}
}
Although the imageText.stringValue actually seems to have changed based on the fact that it prints the updated text (through the console), the text in the window never changes. Why is this happening? The reason is probably really obvious to professionals, but I'm still an amateur and can't figure it out. Thanks.

Are Type Casts an Atomic Operation in Swift? - BAD_ACCESS_ERROR

Let's say I am trying to access a shared variable between two threads. One thread will continuously set the shared variable to either nil or to the reference of an object that can be deallocated.
Class Code
class ConcurrentPrinter {
var value: AnyObject?
}
Thread one
// called 30 times per second
func setter(){
value = shouldSet ? nil : valueArray[0]
// where the value is an instance type
}
Thread two
// also called 30 times per second
func getter() {
if value != nil {
guard let desiredObject = value as? desiredObjectType else {
return
}
}
For some reason, I am getting a Bad_Address error in the guard statement when it tries to cast value into the desiredObjectType. Is this happening because the cast operation gets the address of value and then it gets deallocated before it can finish the cast operation?
Okay, I figured it out. The answer is to place each of the operations on a DispatchQueue and run each of the code using an async request. This ensures that the two pieces of code are running simultaneously

Unable to access variable outside of query function even though variable is declared after class

I have a few queries running to load variables with data from a Parse server into variables that are declared right below class. When I print the variable to the console within the query function, it prints correctly, but when I call it outside of the function or print it, it is empty. Any idea where I'm going wrong?
Variable declaration:
class AddTaskViewController: UIViewController, UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
var varCost = ""
Query loading variable:
let varCostQuery = PFQuery(className: "Class1")
varCostQuery.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error!)
} else {
varCostQuery.whereKey("Header", equalTo: self.HeaderSelection.text)
varCostQuery.findObjectsInBackground(block: { (objects, error) in
for object in objects! {
self.varCost = object["pricePerUnit"] as! String
print(self.varCost) //Option1
}
})
}
})
print(varCost) //Option2
When I print Option1, I get the data exactly like I'm looking for, but when I print Option2 or try to do anything with the varCost variable at this level, I get "" like the variable has never been updated.
This occurs because the code being passed to the findObjectsInBackground method is code that is run only once all the objects have been found.
Because this code will only be called when the objects have been found, this may take a bit of time, so this code is send to a background queue to wait for the objects to be found.
We don't want the rest of our code to pause and wait for this though! That would slow our program down a lot. So the program continues past this block until it completes. That's why Option 2 is empty, because findObjectsInBackground hasn't had time to get the objects yet so the code has jumped straight to where Option 2 is.
When the objects have finally been found, the block of code is called and Option 1 is printed.
This means that you can only be sure self.varCost will have the right value from within this block (or closure, as it is called in Swift).

Swift 2.2: Optional Binding in a function

Heys guys,
I am pretty new into programming and therefore I've followed I course on Udemy to teach me Swift 2.2.
For learning purpose I have been trying to program a BMI-calculator where I have a textfield (which just displays the value) and a slider where I can put my weight in kg. After dragging the slider the value is visible in the textfield. I cannot put a value into the textfield so that it is displayed on the slider!
The same textfield-slider relation is used with the height in cm. Now I created an IBAction the bring my kgSlider.value into my kgField.text and it looks like this:
#IBAction func kgSet(sender: AnyObject) {
kgField.text! = String(Int(kgSlider.value))
}
Thats works fine but I unwrapped (like the teacher in the course) without knowing, if there really is a value. Okay, I know in this case that there will be a value, but I would like to go more real and therefore I tried to use an Optional-Binding to find out, if there is a value instead of directly unwrap it.
Therefore I used the cm.Field and the cm.Slider in my code but it doesn't work for now and I don't know why. The code is the following:
#IBAction func cmSet(sender: AnyObject) {
if let tempCm = String(Int(cmSlider.value)) as String! {
cmField.text = tempCm
}
}
So I created the constant called tempCM which will got the value from the cmSlider, if there is a value. Therefore I casted the cmSlider.value like in the other IBAction into an Int and then into a String. If there is the value it will carry it into the cmField.text. This didn't work, therefore I tried to use the "as String!" statement but know I get always 0 instead of the correct value.
So what am I doing wrong there?
So, this should compile fine and provide you with your desired result.
#IBAction func cmSet(sender: AnyObject) {
if let tempCm = String(Int(cmSlider.value)) {
cmField.text = tempCm
}
}
You could also try this
cmField.text = String(Int(cmSlider.value)) ?? " "
in the second example, you are using the optional operator to say if you can convert this to an Int then that Int to a string set the cmField.text property to its value, otherwise use a blank space as the default value.

Updating SwiftyJSON object created from an Alamofire request

I'm trying to display some JSON data pulled from a website in a table view. Initially, everything works great. I declare my JSON data as an instance varable:
var tableData:JSON!
{
didSet
{
tableView.reloadData()
}
}
And then I make my request somewhere-- viewDidLoad or viewWillAppear or via the user pulling down on the tableView to refresh or whatever, doesn't really matter-- set the data, and the table gets reloaded. Everything works great.
request(method, url, parameters: parameters, encoding: ParameterEncoding.URL).responseJSON(options: NSJSONReadingOptions.AllowFragments) { (request, response, json, error) -> Void in
self.tableData = JSON(json!)
}
However, sometimes I don't just want to display the data, but I want to let the user update the data, for example, in a textField. And here's where I'm running into problems.
So I've created a table view cell with a text field, filled it with some text pulled from my tableData JSON, so far so good. My user can then change the text field, and I want to update the tableData via the text field's delegate methods. This is not working for me.
func textFieldDidEndEditing(textField: UITextField)
{
let key = "anExampleKey"
tableData[key] = JSON(textField.text)
println(tableData[key]) // the original value, not textField.text
}
Nothing seems to happen, the tableData value for "anExampleKey" remains unchanged. So, if that cell scrolls off the screen for example and then comes back into view, any changes my user has made are lost and it reverts back to the original value.
This seems to be an issue solely with a JSON value created from an Alamofire request however. For example, if I do this:
func textFieldDidEndEditing(textField: UITextField)
{
let key = "anExampleKey"
var json = JSON([key:tableData[key].stringValue])
tableData[key] = JSON(textField.text)
json[key] = JSON(textField.text)
println(tableData[key]) // the original value, not textField.text
println(json[key]) // textField.text
}
The json variable will be updated to reflect the textField, while the tableData will still have its original value, not what's in the textField. Does anyone know why the JSON object made via the Alamofire request is immutable, whereas one created directly from a dictionary is not?
Ok, I figured it out. It has nothing to do with SwiftyJSON or Alamofire, but rather the fact that I declared my instance variable as optionally unwrapped. I'm still not entirely sure about all the rules for optional and implicitly unwrapped variables, but using regular JSON variable instead of JSON! allowed me to update it.
var tableData:JSON! /* won't update */
var tableData:JSON = JSON([:]) /* will update */
I have no idea why this is the case. I thought I had my head around the Swift optional and implicitly unwrapped rules by now, but I guess not. It's almost like every time I implicitly unwrap a JSON! variable, it creates a new copy and updates that, leaving the instance variable unchanged. I fired up a playground to see if that was true for all implicitly unwrapped structs, and it's not, I can update the instance values for an implicitly unwrapped struct just fine in there. Interesting, optional JSON? values do update.
struct Test
{
var myVar = 0
}
var myTest:Test!
myTest = Test() /* myVar = 0 */
myTest.myVar = 7 /* myVar = 7 */
myTest.myVar /* myVar = 7 still */
var implicitlyUnwrappedJSON:JSON!
implicitlyUnwrappedJSON = JSON(["Test":"This is one"])
implicitlyUnwrappedJSON["Test"] = "This is another one"
implicitlyUnwrappedJSON["Test"].string /* This is one */
implicitlyUnwrappedJSON?["Test"] = "This is another one"
implicitlyUnwrappedJSON["Test"].string /* This is another one */
implicitlyUnwrappedJSON!["Test"] = "This is another one yet"
implicitlyUnwrappedJSON["Test"].string /* This is another one yet */
var optionalJSON:JSON?
optionalJSON = JSON(["Test":"This is one"])
optionalJSON?["Test"] = "This is another one"
optionalJSON?["Test"].string /* This is another one */
optionalJSON!["Test"] = "This is another one yet"
optionalJSON!["Test"].string /* This is another one yet */
var json:JSON = JSON(["Test":"This is one"])
json["Test"] = "This is another one"
json["Test"].string /* This is another one */
So I don't have the slightest clue what is going on here, but I have it working.