How to UITest UIProgressView? - ios

Situation:
I want to UI-test my progress view and see if it's progress has increased.
What I've tried:
By using a breakpoint in my UI-test and using the po print(app.debugDescription), I can successfully see and access the UIProgressView element.
It's called:
Other, 0x6080003869a0, traits: 8589935104, {{16.0, 285.5}, {343.0, 32.0}}, identifier: 'progressView', label: 'Progress', value: 5%
I access it with let progressView = secondTableViewCell.otherElements["progressView"], which works, as .exists returns true.
Now, to check progress, the description above hints at the 'value' property, that is currently at 5%. However, when I try progressView.value as! String or Double or whichever, I cannot seem to get the property as it isn't NSCoder-compliant.
Question:
Is it possible to UI-test a UIProgressView and use it's progress value?
My final custom solution:
Using the value property (when casting to String) somehow does work now. As I wanted to check if progress increased over time, I strip the String to get the numeric progress in it to compare. Here is my solution I use now:
// Check if progress view increases over time
let progressView = secondTableViewCell.otherElements["progressView"]
XCTAssert(progressView.exists)
let progressViewValueString = progressView.value as! String
// Extract numeric value from '5%' string
let progressViewValue = progressViewValueString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
// Let progress increase
sleep(3)
let newProgressViewValueString = progressView.value as! String
let newProgressViewValue = newProgressViewValueString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
XCTAssert(newProgressViewValue > progressViewValue)

XCUIElement conforms to XCUIElementAttributes which gives you access to a value property of type Any?
The actual type of value at runtime depends on which kind of UI element you are querying. In case of a UIProgressView it is of type __NSCFString, which is a concrete subclass of NSString. Which means you can safely cast it to String:
This works (provided that you set your progress view's accessibility identifier to "progressView"):
func testProgressView() {
let app = XCUIApplication()
app.launch()
let progressView = app.progressIndicators["progressView"]
// alternative (if the above query does not work):
// let progressView = app.otherElements["progressView"]
let currentValue = progressView.value as? String
XCTAssertEqual(currentValue, "5 %")
}
Weirdly in some cases the progressIndicator query does not work. In that case you can try a otherElements query (see comment in above code)

Related

Unable to change ui label in swift

I am trying to make a weather app. the issue I am having is after I get the data parsed out I try to change the UILabel to equal the data that came back example.
if let weather = item["weather"] as? NSArray{
if let value = weather[0] as? NSDictionary{
if let description = value["description"] as? String{
print(description)
}
instead of printing the description I want to change the content of the UI Label like this
conditionlabel.text = description
but I get this error Reference to property 'Condition' in closure requires explicit 'self.' to make capture semantics explicit.
however, every time I try the self. in front it does not print my data and something else happens. Any help would be much appreciated as I am new to this
Create an optional variable in controller that has you UILabel:
var descriptionText: String?
Now when you parse data, assign value to this variable
if let description = value["description"] as? String{
descriptionText = description
}
After data is parsed assign this value to the label
self.conditionlabel.text = descriptionText
I think what you need to do here is use self.conditionlabel.text = description (that is, the use of self is for the UILabel rather than description - my guess is that you were using self.description which gives you a description of the view controller.)
EDIT:
To overcome the threading issue, wrap the code like this:
DispatchQueue.main.async { [weak self] in
self?.conditionlabel.text = description
}

How to retrieve variable data from a label in UI tests

This is what I have so far and I am simply trying to get the result that is output in a label and test it against set results:
func testExample() {
let app = XCUIApplication()
let enterRomanNumeralsHereTextField = app.textFields["Enter roman numerals here"]
enterRomanNumeralsHereTextField.tap()
let convertButton = app.buttons["Convert"]
//1
enterRomanNumeralsHereTextField.typeText("LL")
convertButton.tap()
XCTAssertTrue(app.staticTexts.element(matching:.any, identifier: "Roman Numerals").label == "NotValidRomanNumber")
//2
enterRomanNumeralsHereTextField.typeText("LXXXIX")
convertButton.tap()
XCTAssertEqual(app.otherElements.containing(.staticText, identifier:"Roman Numerals").element.value as! String, "89")
//3
enterRomanNumeralsHereTextField.typeText("")
enterRomanNumeralsHereTextField.typeText("LXXVIII")
convertButton.tap()
XCTAssertEqual(app.otherElements.containing(.staticText, identifier:"Roman Numerals").element.value as! String, "")
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
The first attempt(labeled 1) gives me a build error saying "cannot call value of non function type XCUIElement.
The second attempt builds but the test fails because even though the label does indeed produce the right value, the test is reading the label as blank which brings us to my third attempt which passes the test because I compared it to a blank label, which is what its showing.
So as I said above I'm just wondering how exactly to retrieve the label value that is a result of a "calculation" or button press.
Unfortunately when a UILabel is accessed in a UITest the XCUIElement's value property is not set with the text property of the UILabel. It is always empty.
XCUIElement also has a label property which you can use for your purpose. You only have to make sure that you are not setting the accessibilityLabel on your UILabel. Use accessibilityIdentifier instead:
In your app:
label.accessibilityIdentifier = "Roman Numerals"
In your UITest:
XCTAssertEqual(app.staticTexts.element(matching:.any, identifier: "Roman Numerals").label, "89")

What is payload data in iOS?

I'm debugging my program to check if the value of a property is correctly set. I put a breakpoint in this function:
func showContent(data: Any) -> UIView {
// breakpoint here
var contentView = UIView()
if let image = data as? UIImage {
let imageView = UIImageView()
imageView.image = image
contentView = imageView
}
if let text = data as? String {
let label = UILabel()
label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
label.text = text
contentView = label
}
return contentView
}
The value passed to this function is from a view controller:
override func viewDidLoad() {
calcGroupFamiliarity()
flashCardView.linkedMemory = Memory(masteryLevel: 1, algorithm: Algorithm.algorithm1.chooseAlgorithm(), forgetRatio: 0, lastStudyTime: Date(), front: #imageLiteral(resourceName: "Ideas-Blue"), back: #imageLiteral(resourceName: "Ideas-Yellow"))
}
As you can see, both the front and the back are images, however, in the debugger, they both appeared as some payload_data, while the data type of other values such as masteryLevel and algorithm are correct:
Can somebody explain what that means? And what should I do to pass the normal image data instead?
Update:
This is Memory class:
class Memory: NSObject, NSCoding {
var masteryLevel: Int
var algorithm: [Int: Double]
var forgetRatio: Int
var lastStudyTime: Date
var strength: Double = 0
var front: Any
var back: Any
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask)[0]
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Memory")
init(masteryLevel: Int, algorithm: [Int: Double], forgetRatio: Int, lastStudyTime: Date, front: Any, back: Any){
self.masteryLevel = masteryLevel
self.algorithm = algorithm
self.forgetRatio = forgetRatio
self.lastStudyTime = lastStudyTime
self.front = front
self.back = back
}
...
}
This is a detail of how Swift implements the Any type. Given that Swift can't know beforehand what you are putting into e.g. front, it needs to store a pointer to that object (in payload_data_0) as well as a pointer to metadata about the stored object's type (in instance_type). As far as I know, payload_data_1 and payload_data_2 are there as an optimization so that Swift can store up to 24-byte large structs in place rather than having to put them in a wrapper object that needs to be stored in a separate heap location.
So, to answer your question: This is neither a bug in Swift, nor an error on your side. Your data gets stored and accessed in the right way. If you would prefer to inspect front more easily while debugging, try
(lldb) po front
in the debugger. Alternatively, change front's type to e.g. UIImage?. If that is not possible, you could declare a protocol
protocol MemoryStoreable: class { }
and extend every type that needs to be stored in those fields like so:
extension UIImage: MemoryStoreable { }
and declare the variable as
var front: MemoryStoreable?
Note that MemoryStoreable is restricted to classes, as otherwise a protocol witness would need to be stored (which is again implemented similarly to the Any field you observed).
Another alternative would be, if you know you'll store e.g. only images and strings in front, to use an enum:
enum MemoryStoreable {
case `string`(String)
case image(UIImage)
}
var front: MemoryStoreable?
That will still require at least 25 bytes of storage in your object (possibly 32 due to memory alignment constraints), though, as that's the size of the String struct.
For more details, see e.g. https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html.
Base on #MrMage's awesome answer.
#MrMage gave 2 suggestions on approaching this problem, it took some
effort for me to grasp them, for what I wanted to achieve in the
program, it's the perfect scenario for me to go
protocol-oriented-programming.
Here's a really easy to understand tutorial that help me a lot:
Introduction to Protocol Oriented Programming in Swift
Hopefully this can be helpful to you :)

Receiving Data using Firebase swift

I am trying to retrieve Childs from The Firebase Database using this code (which is declared in viewdidload)
let rootRef = FIRDatabase.database().reference().child("UsersInfo").child((FIRAuth.auth()?.currentUser?.uid)!)
rootRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
let hello = snapshot.value as! [String : AnyObject]
let usernamerecieved = hello["Username"] as! String
let Emailrecieved = hello["Email"] as! String
let bloodtyperecieved = hello["BloodType"] as! String
globalusername = usernamerecieved (EDITED)
})
and I've declared a global variable as such
var globalusername = "user"
Im trying to extract the usernamerecieved variable and casting it on a Global Variable. However when I print the global variable later on in the viewdidLoad I still get the initial value of the globalusername which is "user".
In this line of code : usernamerecieved = globalusername what you are doing in this line is that you are setting the value of the usernamerecieved to the value of globalusername i.e. 'user'
It should be globalusername = usernamerecieved.
Also sometimes it takes time to retrieve data from the server reasons could be numerous - slow network connection, heavy data retrieving such as images etc. but before it could be completed (Retrieving Action From The Server) it might be possible that line in which you are printing the globalusername gets called first , even before the completionBlock off the observeEventType is even completed (since its asynchronous , somewhat similar to dispatch_async).To tackle that you should call the printing call for globalusername inside the completionBlock or observe any change in it's value by using addObserver to the globalusername,which will let you know whenever the value of globalusername is changed or updated so that you can act accordingly .
observeEventType's block is asynchronous, it is possible that by the time you read the globalusername var, the block has not been completed yet.
Try printing globalusername inside the block and see the value

How to get the current word being typed?

I am making a custom keyboard extension for iOS 8 and am unsuccessful at trying to reflect the current word being typed on a UILabel sitting on top of the keyboard (think autocorrect). So far the code I wrote reflects the sentence before the cursor and not as it's being written, but as the cursor is moved from one position to another. What I am trying to achieve is exactly like the first autocorrect box in the native keyboard. Would anyone mind telling me what I am doing wrong?
Code:
override func textWillChange(textInput: UITextInput) {
var tokens = (self.textDocumentProxy as! UITextDocumentProxy).documentContextBeforeInput .componentsSeparatedByString(" ") as NSArray
var lastWord = tokens.lastObject as! String
println(lastWord)
bannerView?.btn1.setTitle(lastWord, forState: .Normal)
}
I've tried setting a condition whereby if beforeCursor contained either a space/period/comma to set the button title as "" but that is not efficient in the long run as I need to obtain words in order to be able to make an autocorrect feature.
Edit:
I've figured out how to get the word before the cursor (updated the code above), but not how to update the label as each letter is being added. func textWillChange(textInput: UITextInput)isn't working out. It's not me it's her.
Thanks!
You should use the textDocumentProxy property of your UIInputViewController:
let proxy = self.textDocumentProxy as! UITextDocumentProxy
To get the word being typed, I would suggest something like this:
var lastWordTyped: String? {
if let documentContext = proxy.documentContextBeforeInput as NSString? {
let length = documentContext.length
if length > 0 && NSCharacterSet.letterCharacterSet().characterIsMember(documentContext.characterAtIndex(length - 1)) {
let components = documentContext.componentsSeparatedByCharactersInSet(NSCharacterSet.alphanumericCharacterSet().invertedSet) as! [String]
return components[components.endIndex - 1]
}
}
return nil
}

Resources