Show please wait overlay in swift asynchronously - swift

I am using the answer located here for creating a "please wait" overlay. And I also used this answer for the asynchronous logic. What I am trying to do is create this overlay asynchronously. Ultimately, what I would like to do is just create a method which shows this overlay, and when that method call returns I want to be sure that the overlay has been fully presented. So something like this:
Helper.showPleaseWaitOverlay()
doSomeOtherTask() // when we get here, overlay should be fully presented
Helper.hidePleaseWaitOverlay()
I realize I could do something like this (e.g. use the completion callback of the present method) :
Helper.showPleaseWaitOverlay() {
doSomeOtherTask()
Helper.hidePleaseWaitOverlay()
}
But I really am just curious as to why the below code doesn't work. What ends up happening is that the group.wait() call just hangs and never returns.
What am I doing wrong?
// create a dispatch group which we'll use to keep track of when the async
// work is finished
let group = DispatchGroup()
group.enter()
// create the controller used to show the "please wait" overlay
var pleaseWaitController = UIAlertController(title: nil, message: "Please Wait...", preferredStyle: .alert)
// present the "please wait" overlay as an async task
DispatchQueue.global(qos: .default).async {
// we must perform the GUI work on main queue
DispatchQueue.main.async {
// create the "please wait" overlay to display
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();
self.pleaseWaitController!.view.addSubview(loadingIndicator)
self.present(self.pleaseWaitController!, animated: true) {
// the "please wait" overlay has now been presented, so leave the dispatch group
group.leave()
}
}
}
// wait for "please wait" overlay to be presented
print("waiting for please wait overlay to be presented")
group.wait() // <---- Call just hangs and never completes
print("done waiting for please wait overlay to be presented")

Related

WatchOS warning: rejected resignFirstResponder when being removed from hierarchy

I'm getting this error in Xcode.
2018-02-26 07:13:22.326888-0500 Watch Extension[1298:2691330] [View]
First responder warning: '<SPInterfacePicker: 0x14dc1740; frame = (76
0; 58 44); gestureRecognizers = <NSArray: 0x14dcd8a0>; layer =
<CALayer: 0x14dc1910>>' rejected resignFirstResponder when being
removed from hierarchy
My InterfaceController has 4 WkInterfacePickers and it seems like this error might be related to presenting an alert (when the user saves data), but I am not sure.
Has anyone else ever seen this?
My code:
if successSaving == true {
DispatchQueue.main.async {
WKInterfaceDevice.current().play(.success)
self.showSuccessAlertWith(message: "Workout Saved, Stats Added.")
}
func showSuccessAlertWith(message: String){
let action1 = WKAlertAction(title: "OK", style: .default) {
WKInterfaceController.reloadRootPageControllers(withNames: ["InterfaceController"],
contexts: nil,
orientation: .vertical,
pageIndex: 0)
}
presentAlert(withTitle: "Success", message: message, preferredStyle: .alert, actions: [action1])
}
}
I think what is happening is that WKInterfacePickers are very easy to accidentally leave in a "still editing" state, in other words if you scroll through the values then tap "Done" button (which calls reloadRootPageControllers) the system thinks the user was still in the process of editing the picker's value.
I can just ask users to be more careful (unlikely)...but I am unsure how to solve which in iOS the equivalent would be calling resignFirstResponder.
Neither resignFocus nor setting isActive to false, according to my testing, prevent this message from logging.

How to prevent NSAlert from dismiss

How can I prevent a simple NSAlert from being dismissed?
F.ex., when the application launches, I show a NSAlert with a NSTextField included.
The user shall type in a password. Only if the password is correct, the user should be allowed to use the application, if not (if the password is not correct), the Alert should stay there and ask again for the password.
This is my code so far (to create the alert):
func applicationDidFinishLaunching(_ aNotification: Notification){
let alert = NSAlert()
alert.addButton(withTitle: "Send")
alert.delegate = self
alert.alertStyle = .informational
alert.messageText = "Password - Login"
alert.informativeText = "Please type in your password: "
let txt = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
txt.stringValue = "Password:"
alert.accessoryView = txt
alert.beginSheetModal(for: NSApplication.shared().mainWindow!) { (response) in
if (response == NSAlertFirstButtonReturn) {
// the alert closes here, is there any way to prevent this?
} else {
print("No value.")
}
}
OS: OS X Sierra,
Swift 3
You can present the alert a second time; if you need to customize the behavior beyond that, you'll need to eschew NSAlert and run an NSWindow or NSPanel of your own making.

Activity indicator while creating csv file

I am trying to show an activity indicator while creating a csv file, but it does not show. I am guessing I should use dispatch_async somehow, but I cant figure out how to do this in swift 3.
var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
override func viewDidLoad() {
super.viewDidLoad()
// activity indicator
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 100 ,y: 200,width: 50,height: 50)) as UIActivityIndicatorView
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
}
func writeToCsv() {
self.activityIndicator.startAnimating() // start the animation
let fileName = "events.csv"
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
var csvText = self.name! + "\n"
csvText += "Date,Start time,End time\n"
// create rest of comma-separated string
for event in self.events! {
let newLine = "\(event.date),\(event.startTime),\(event.endTime)\n"
csvText.append(newLine)
}
// write to csv
do {
try csvText.write(to: path!, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("Failed to create file")
print(error)
}
// create and present view controller with send options
let vc = UIActivityViewController(activityItems: [path as Any], applicationActivities: [])
self.present(vc, animated: true, completion: nil)
self.activityIndicator.stopAnimating() // stop the animation
}
Err, alright bit hard to answer this without a bit more context about your view setup. First of all, make sure your activity indicator is visible without calling the writeCsv method, so you know your view hierarchy is correct. ( I.E. It could be that it is hidden behind some other subview )
Next, in Swift3 Dispatch has been changed to a newer API. I'm not sure whether on OSX they use the raw libdispatch Swift wrapper, but in any case you access it like this:
Background default queue:
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { /* code */ }
Main thread:
DispatchQueue.main.async { /* Mainthread code ( UIKit stuff ) */ }
Your own custom queue for CSV generation blocks:
let queue = DispatchQueue(label: "csvgenerator.queue")
queue.async { /* code */ }
Now for your animating / stopAnimation, make sure you call your UIKit related code from the mainthread to prevent weird glitechs and or crashes
Namely:
DispatchQueue.main.async {
self.activityIndicator?.startAnimating()
}
Another good idea might be to use NSOperationQueue instead. It internally uses GCD I believe, but it does integrate very well into iOS and might make some of the dispatching a lot easier to implement. I myself always use GCD instead, but I have never really had long queeu's of work that needed to be done. One of the advantages of NSOperationQueue is that it is a lot more user friendly in terms of cancelling dispatch blocks.
An interesting session video about NSOperationQueue in the WWDC app written by Dave Delong: WWDC Videos 2015
A small minor changes I'd make to your writeCSV method:
guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) else {
// Should throw an error here, or whatever is handy for your app
return
}
Try to avoid forced unwrapping at all stages where possible.
In methods that have this, you can for instance add "throws" to the end of the function definition so you can use try without the do and catch block, while also being able to throw errors in your guard statement so whatever calls writeCsv can catch the error and more easily display it to the user.

Swift: How to let background thread wait for user input?

I have a background thread which needs some user input. Since it is not recommended to call an NSAlert window from a background thread, I like to do this in the main thread. But how can I let the background thread wait till the NSAlert window is closed?
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// some background task
dispatch_async(dispatch_get_main_queue()) {
// UI task asking for user input:
let alert = NSAlert()
alert.messageText = "Some text"
alert.informativeText = "Some information"
alert.addButtonWithTitle("Yes")
alert.addButtonWithTitle("No")
result = alert.runModal()
}
// some background task, treating user input (Yes/No)
}
The runModal method is synchronous. If you want to wait for it to finish, rather than dispatching asynchronously to the main queue with dispatch_async, you can most easily achieve what you asked for by dispatching it synchronously with dispatch_sync, e.g.:
let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(queue) {
// some background task
var result: NSModalResponse!
dispatch_sync(dispatch_get_main_queue()) { // note, `dispatch_sync`
// UI task asking for user input:
let alert = NSAlert()
...
result = alert.runModal()
}
// some background task, treating user input (Yes/No)
}
A more subtle problem rests with the synchronous nature runModal. This will block the main thread. This is not generally a good idea. It can interfere with other tasks your app might be performing (timers, animations, network operations using the main queue, etc.) that might require the main thread.
So, instead of the synchronous runModal, you might consider presenting the NSAlert asynchronously with beginSheetModalForWindow, with a completion handler that dispatches additional background processing to the background queue, e.g.:
let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(queue) {
// some background task
dispatch_async(dispatch_get_main_queue()) { // note, `dispatch_async` is OK here
// UI task asking for user input:
let alert = NSAlert()
...
alert.beginSheetModalForWindow(NSApp.mainWindow!) { result in
dispatch_async(queue) {
// some background task, treating user input (Yes/No)
}
}
}
}
This achieves what you want (triggering additional background tasks when the modal is dismissed), but has the virtue of not blocking the main thread at all.
You can use semaphore.
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// some background task
//Create a semaphore with count = 0
let semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_main_queue()) {
// UI task asking for user input:
let alert = NSAlert()
alert.messageText = "Some text"
alert.informativeText = "Some information"
alert.addButtonWithTitle("Yes")
alert.addButtonWithTitle("No")
result = alert.runModal()
//Signal semaphore
dispatch_semaphore_signal(semaphore)
}
//Wait for semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
// some background task, treating user input (Yes/No)
}
You should not "wait" in the main thread. But background threads are other things.

Why does the iPhone seem to freeze when using a while loop with swift?

I am trying to take a picture every 2 seconds by using a while loop. but when I try this the screen freezes.
This is the function that takes the photo:
func didPressTakePhoto(){
if let videoConnection = stillImageOutput?.connectionWithMediaType(AVMediaTypeVideo){
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {
(sampleBuffer, error) in
if sampleBuffer != nil {
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
let dataProvider = CGDataProviderCreateWithCFData(imageData)
let cgImageRef = CGImageCreateWithJPEGDataProvider(dataProvider, nil, true, .RenderingIntentDefault)
let image = UIImage(CGImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.Right)
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
//Adds every image taken to an array each time the while loop loops which will then be used to create a timelapse.
self.images.append(image)
}
})
}
}
To take the picture I have a button which will use this function in a while loop when a variable called count is equal to 0, but when the end button is pressed, this variable is equal to 1, so the while loop ends.
This is what the startPictureButton action looks like:
#IBAction func TakeScreanshotClick(sender: AnyObject) {
TipsView.hidden = true
XBtnTips.hidden = true
self.takePictureBtn.hidden = true
self.stopBtn.hidden = false
controls.hidden = true
ExitBtn.hidden = true
PressedLbl.text = "Started"
print("started")
while count == 0{
didPressTakePhoto()
print(images)
pressed = pressed + 1
PressedLbl.text = "\(pressed)"
print(pressed)
sleep(2)
}
}
But when I run this and start the timelapse the screen looks frozen.
Does anyone know how to stop the freeze from happening - but also to add each image taken to an array - so that I can turn that into a video?
The problem is that the method that processes clicks on the button (TakeScreanshotClick method) is run on the UI thread. So, if this method never exits, the UI thread gets stuck in it, and the UI freezes.
In order to avoid it, you can run your loop on the background thread (read about NSOperation and NSOperationQueue). Occasionally you might need to dispatch something from the background thread to the UI thread (for instance, commands for UI updates).
UPDATE: Apple has a really great documentation (best of what I've seen so far). Have a look at this: Apple Concurrency Programming Guide.
You are calling the sleep command on the main UI thread, thus freezing all other activity.
Also, I can't see where you set count = 1? Wouldn't the while loop continue forever?