Using XCode (Swift) to develop iOS app for school and label properties won't refresh - ios

I am a reading teacher (I don't teach programming) at a school for students with LDs. We needed an app to flash 20 words in a random order for a set number of milliseconds, then black out the word for a set number of ms, then proceed through the list of 20 words. I dabbled in programming 15 years ago but have never developed an app.
The logic works ok, but I know there are better ways to do it - I just don't have time to learn all the ins and outs right now! My only problem is that the label won't update until the entire routine completes so no words show. I have tried a bunch of different techniques recommended on this site, but have not gotten anything to work. Is there a simple solution - seems like the layoutIfNeeded is a recommended technique but I don't seem able to implement correctly.
Any help is appreciated!
#IBAction func cmdStart(_ sender: Any) {
// the main logic
self.view.layoutIfNeeded()
var x = arc4random() % 20;
var i:Int = Int(x)
var num = 0
while num < 20 {
num += 1
self.lblDisplay.backgroundColor=UIColor.white
while wordUsed[i] == 1
{
x = (arc4random() % 20)
i = Int(x)
}
// output
print ("word is \(wordList[i])")
self.lblDisplay.text = wordList[i]
self.lblDisplay.backgroundColor=UIColor.white
self.view.layoutIfNeeded()
//delay based on slider
let delTimeWord = (self.sldDelay.value)*1000
usleep(useconds_t(delTimeWord))
//blackOut duration based on slider
let delTimeBlack = (self.sldVisible.value)*1000
usleep(useconds_t(delTimeBlack))
self.lblDisplay.backgroundColor = UIColor.black
wordUsed[i]=1
}
}

In an iOS application, you need to let the "event loop" run. This is a loop run by the operating system that checks for input events, refreshes the screen, and generally keeps everything going. If you don't let the loop run, then an app "locks up" and stops doing anything.
The cmdStart method, which I assume is triggered by a button, is itself being called by the event loop. Rather than running your own loop within that method, what you should do is have that method set some state and maybe start a timer that will do things later, and then return so that the event loop can keep running.
So those variables that you currently have in your cmdStart should probably be properties of the view controller. Instead of calling usleep, start a timer that will fire at the appropriate time, and in the timer handler, do whatever the "next step" is.
You don't need to call layoutIfNeeded. Just set the label's text and color within your handler method, and when it returns the event loop will take care of updating the screen,
Sorry that I can't provide a complete example, but I'd recommend reading about NSTimer and looking for examples.

Related

How to check an element's existence after a loading animation in a UI Test? Swift

The problem is simple: How to check an element's existence after a loading animation in a UI Test? BUT without using static timeouts.
For example: If animation lasts 3 seconds ui test waits for 3 seconds, if animation lasts 10 seconds ui test waits for 10 seconds or is there another way around it?
I'm trying to wait as long as my api call lasts simply
You can tackle this in one of two two ways: 1) Wait for your element to exist or 2) Wait for the animation to disappear.
In order to wait for your element to exist, you'd use Apple's waitForExistence function with a long timeout on your target element. This returns a boolean so you can simply assert directly on it.
XCTAssertTrue(myElement.waitForExistence(timeout: 15.0)) // wait for 15 seconds maximum
In order to wait for your animation to disappear, you'd identify it, and extend XCUIElement with the following function, which I use extensively and therefore bundle into my XCToolbox Cocoapod. You'd then be able to check the exists property on your target element.
public func waitForDisappearance(timeout: TimeInterval = Waits.short.rawValue) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: UIStatus.notExist.rawValue), object: self)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
switch result {
case .completed:
return true
default:
return false
}
}
This code would look like the following:
_ = animationElement.waitForDisappearance(timeout: 15.0)
XCTAssertTrue(myElement.exists)
Neither solution is wrong. The first is less code and arguably cleaner, the second is more explicit and possibly more readable.

UI not updating (in Swift) during intensive function on main thread

I wondered if anyone could provide advice on how I can ‘force’ the UI to update during a particularly intensive function (on the main thread) in Swift.
To explain: I am trying to add an ‘import’ feature to my app, which would allow a user to import items from a backup file (could be anything from 1 - 1,000,000 records, say, depending on the size of their backup) which get saved to the app’s CodeData database. This function uses a ‘for in’ loop (to cycle through each record in the backup file), and with each ‘for’ in that loop, the function sends a message to a delegate (a ViewController) to update its UIProgressBar with the progress so the user can see the live progress on the screen. I would normally try to send this intensive function to a background thread, and separately update the UI on the main thread… but this isn't an option because creating those items in the CoreData context has to be done on the main thread (according to Swift’s errors/crashes when I initially tried to do it on a background thread), and I think this therefore is causing the UI to ‘freeze’ and not update live on screen.
A simplified version of the code would be:
class CoreDataManager {
var delegate: ProgressProtocol?
// (dummy) backup file array for purpose of this example, which could contain 100,000's of items
let backUp = [BackUpItem]()
// intensive function containing 'for in' loop
func processBackUpAndSaveData() {
let totalItems: Float = Float(backUp.count)
var step: Float = 0
for backUpItem in backUp {
// calculate Progress and tell delegate to update the UIProgressView
step += 1
let calculatedProgress = step / totalItems
delegate?.updateProgressBar(progress: calculatedProgress)
// Create the item in CoreData context (which must be done on main thread)
let savedItem = (context: context)
}
// loop is complete, so save the CoreData context
try! context.save()
}
}
// Meanwhile... in the delegate (ViewController) which updates the UIProgressView
class ViewController: UIViewController, ProgressProtocol {
let progressBar = UIProgressView()
// Delegate function which updates the progress bar
func updateProgressBar(progress: Float) {
// Print statement, which shows up correctly in the console during the intensive task
print("Progress being updated to \(progress)")
// Update to the progressBar is instructed, but isn't reflected on the simulator
progressBar.setProgress(progress, animated: false)
}
}
One important thing to note: the print statement in the above code runs fine / as expected, i.e. throughout the long ‘for in’ loop (which could take a minute or two), the console continuously shows all the print statements (showing the increasing progress values), so I know that the delegate ‘updateProgressBar’ function is definitely firing correctly, but the Progress Bar on the screen itself simply isn’t updating / doesn’t change… and I’m assuming it’s because the UI is frozen and hasn’t got ‘time’ (for want of a better word) to reflect the updated progress given the intensity of the main function running.
I am relatively new to coding, so apologies in advance if I ask for clarification on any responses as much of this is new to me. In case it is relevant, I am using Storyboards (as opposed to SwiftUI).
Just really looking for any advice / tips on whether there are any (relatively easy) routes to resolve this and essentially 'force' the UI to update during this intensive task.
You say "...Just really looking for any advice / tips on whether there are any (relatively easy) routes to resolve this and essentially 'force' the UI to update during this intensive task."
No. If you do time-consuming work synchronously on the main thread, you block the main thread, and UI updates will not take effect until your code returns.
You need to figure out how to run your code on a background thread. I haven't worked with CoreData in quite a while. I know it's possible to do CoreData queries on a background thread, but I no longer remember the details. That's what you're going to need to do.
As to your comment about print statements, that makes sense. The Xcode console is separate from your app's run loop, and is able to display output even if your code doesn't return. The app UI can't do that however.

iOS: OperationQueue.schedule(after: Date) that cannot be triggered by date change

Problem
I need to get a callback when at least X amount of time has passed since the date for the callback has been set.
Example 1:
This would have worked great, but it's possible to trigger an execution of the block by setting the date earlier than the correct time right now:
let responseDate = Date().advanced(by: 60) // 1 min
OperationQueue.current.schedule(after: .init(responseDate), {
print("the time is now!") // possible to set the current date 1 min before
}
On the other hand, the solution for getting a current uptime from this answer works great, but it requires timer constantly running to check if we're close to date.
Is it possible to combine these two approaches and somehow "attach" a callback to KERN_BOOTTIME, so that the OS will call my method when the boottime reaches a certain value?
I'm looking as well to alternative engineering solutions that satisfy two criterias:
It should not be possible to trigger the callback by resetting the device date to some arbitrary value in the past
If the device has been put to sleep (e.g. by pressing the on/off side button), the clock should still be "ticking", so that the method will be called back while the app is running in the background.
More details:
Backgrounding / app termination is out of scope
The main point is to prevent a bypass by switching the date backwards in the settings.

actionscript 2 go to another screen after variable = 10

I'm making a game where i have a character moving through a maze collecting coins. Once I have collected 10 coins i want my game to automatically go to the end keyframe. The variable Coincounter keeps a record of the number of coins the user has collected so far. So far i have this code to make my game do that....
if (_root.Coincounter == 10) {
trace("got to ten" +_root.Coincounter);
gotoAndStop(4) ;
}
The code is inside an onClipEvent - either keyDown or enterFrame, i can put it in either of them. The trace works fine so it's my gotoAndStop code that doesn't work - i'm not sure why. Thanks for the help.

How could i make this function sleep for a certain amount of time?

So here's my problem. I have code set up that calls a function whenever my player is over its last destination in the a* pathfinding array...
public function rakeSoil(e:Event):void {
var:Cell = Grid.getCellAt(player.x/50, player.y/50);
if (cell.isWalkable == false) {
return;
else {
//here is where i want to do the sleep code so this doesnt happen straight away? If possible.
target.sprites = [grass];
}
}
thanks guys :)
Generally, the "right" way to delay execution of something is to use a Timer.
Hacking up some kind of a sleep function could cause problems, since Flash runs in a single thread, so you won't be able to do anything else while your function is running, including refreshing the screen (making your game appear as if it crashed, or at least started lagging).
If you're absolutely, positively sure you want to do this, you could call the getTimer() function in a loop to see if a certain amount of miliseconds has passed.

Resources