How can I insert a textfield when running tests in swift? - swift

I have this test:
func testLogin() throws {
viewController.email.insertText("email#outlook.com")
viewController.password.insertText("secret")
viewController.btnLogin?.sendActions(for: .touchDown)
XCTAssert(viewController.loggedIn == true)
}
But it gives me this error on the first line when I try to insert the email
Unexpectedly found nil while implicitly unwrapping an Optional value
How can I get this test to work?

This code block looks incomplete...
You can't directly access the UIElements for XCTests.
As a first step need to create application object
var app = XCUIApplication()
then you need to launch the app with
app.launch()
then access respective UI element with accessibilityIdentifier as
let email = app.textFields["identifier_of_emil_textfield"]
UI elements needs to have accessibilityIdentifier to work with XCTest framework. If you haven't assign this then you can as emailTxt.accessibilityIdentifier = "identifier_of_emil_textfield" or directly from identity inspector of respective XIB element.
and at last you can use typeText property to add your text like
email.typeText("email#outlook.com")

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.

Populating textField from global variable fails

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.

NSTextField: Converting String to Int aborts (nil) outside of Xcode

I have a MacOS app that has been working fine for around a year. An NSTextField on a screen containing 20 NSTextFields started aborting with "unexpectedly found nil while unwrapping an Optional value". So far, I've done the following:
Removed and reassigned the textfield's IBOutlet link
Deleted and rebuilt the textfield
This fixes the app when I run in Xcode in my Development account. When I generate an archive and transfer it to my Production account, it still aborts.
I'm going to assume this is still the culprit, since I don't really get much from the core dump (because I probably don't understand what I'm looking at).
Here's my code. (game_number is an Int)
game_number = Int(gameNumberTextField.stringValue)!
When I split the code and do this:
let theNumber = (gameNumberTextField.stringValue)!
game_number = Int(theNumber)
theNumber is a String and correct, but game_number is nil
NSTextField parent class NSControl has a property called integerValue which returns an Int:
game_number = gameNumberTextField.integerValue
You should unwrap the optional value like this:
guard let theNumber = gameNumberTextField.integerValue else {
// Value not found, handle this case
return
}
Hope this helps!

.prepare vs. .select

I have a working connection to a database in an iOS10 app, using SQLite.swift.
I want to select details for a specific university where I have an IHE_ID passed in from another view controller.
I would like to just select the row for that specific university, but I can't get a query to work. I can however loop through all the data with a prepare and then choose the one I need from that, which of course is more resource intensive than I need since I already have the specific IHE_ID passed in as anIHE Int from the sending view controller.
connection is working so omitting that code...
do {
let db = try Connection(destinationDBPath, readonly: true)
let IHEs = Table("IHE")
let IHE_ID = Expression<Int>("IHE_ID")
let IHE_Name = Expression<String>("IHE_Name")
let IHE_COE_Url = Expression<String>("IHE_COE_URL")
let IHE_Sector = Expression<Int>("IHE_Sector")
let IHE_COE_Name = Expression<String>("IHE_COE_Name")
for ihe in try db.prepare(IHEs){
if (ihe[IHE_ID] == anIHE){
// build this string, otherwise ignore rest of dataset (doing this because query isn't working)
buildIHE = "Name: \(ihe[IHE_Name])\n"
buildIHE.append("URL: \(ihe[IHE_COE_Url])\n")
// buildIHE.append("Sector: \(ihe[IHE_Sector])\n")
if (ihe[IHE_Sector] == 0) {
buildIHE.append("Sector: Public\n")
} else {
buildIHE.append("Sector: Private\n")
}
buildIHE.append("College of Education Name: \(ihe[IHE_COE_Name])\n")
}
}
print ("Got through building IHE portion of view")
What I'd like to do is use this instead of the for loop.
if let query = IHEs.select(IHE_ID,IHE_Name,IHE_COE_Url,IHE_Sector,IHE_COE_Name).filter(IHE_ID == anIHE){
print("Query successful for \(anIHE) with name \(query[IHE_Name])")
// more actions to build the string I need would then occur
} else {
print("Query has failed or returned nil")
}
Finally, I'll use the selected elements if I can get the query to work.
I think I probably just have something wrong with my syntax on the query, but any help is appreciated.
The line with the "if let query" has this error in Xcode:
Initializer for conditional binding must have Optional type, not 'Table'.
This leads me to think it's something with my use of the .select statement and just new to using SQLite.swift and swift in general.
Last thing is that anIHE comes into this function as an Int, and IHE_ID is Expression as shown in this code. I'm thinking this may be part of the problem.
The Initializer for conditional binding must have Optional type error means that the expression on the right of the if let v = expr statement is not an Optional: there is no point using if let, and the Swift compiler says that you should just write let v = expr.
And indeed, IHEs.select(...).filter(...) returns a non-optional value of type Table.
It is not the database row you expect, because the query has been defined, but not executed yet. After all, you weren't using db: where would the rows be loaded from?
The solution is to bring back the database connection, and load a single row. This is done with the pluck method.
if let ihe = try db.pluck(IHEs.select(...).filter(...)) {
print("Name \(ihe[IHE_Name])")
}

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.