I'm trying to use – performSelectorOnMainThread:withObject:waitUntilDone: for a Cocoa application that I'm developing in Swift. I need the application to wait until the job is done. Anyway, I have the following lines of code.
func recoverData(path:String) -> Void {
let sheetRect:NSRect = NSMakeRect(0,0,400,114)
let progSheet:NSWindow = NSWindow.init(contentRect:sheetRect, styleMask:NSTitledWindowMask,backing:NSBackingStoreType.Buffered,`defer`:true)
let contentView:NSView = NSView.init(frame:sheetRect)
let progInd:NSProgressIndicator = NSProgressIndicator.init(frame:NSMakeRect(190,74,20,20))
progInd.style = NSProgressIndicatorStyle.SpinningStyle
let msgLabel:NSTextField = NSTextField.init(frame:NSMakeRect(20,20,240,46))
msgLabel.stringValue = "Copying selected file..."
msgLabel.bezeled = false
msgLabel.drawsBackground = false
msgLabel.editable = false
msgLabel.selectable = false
contentView.addSubview(msgLabel)
contentView.addSubview(progInd)
progSheet.contentView = contentView
self.window.beginSheet(progSheet) {(NSModalResponse returnCode) -> Void in
progSheet.makeKeyAndOrderFront(self)
progInd.startAnimation(self)
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority,0)) {
//////////////////////////////////////////////////////////////////////////////////////////////////
self.performSelectorOnMainThread(Selector(self.readData(path)),withObject:path,waitUntilDone:true)
//////////////////////////////////////////////////////////////////////////////////////////////////
}
dispatch_async(dispatch_get_main_queue()) {
progInd.indeterminate = true
self.window.endSheet(progSheet)
progSheet.orderOut(self)
}
}
}
func readData(path:String) -> Void {
print("Hello!?")
}
I'm not sure how I pass path to readData. Xcode requires me to set the argument to something other than nil or nothing. In Objective-C, it would be
[self performSelectorOnMainThread:#selector(readData:) withObject:path waitUntilDone:YES];
Anyway, the application never reaches readData. What am I doing wrong?
Thanks for help.
Why not
self.window.beginSheet(progSheet) {(returnCode) -> Void in
dispatch_async(dispatch_get_main_queue()) {
progInd.startAnimation(self)
self.readData(path)
progInd.indeterminate = true
}
}
At some point you have to call self.window.endSheet(progSheet) to dismiss the sheet and call the completion handler.
Edit:
I guess you actually mean something like this
...
self.window.beginSheet(progSheet) {(returnCode) -> Void in
progInd.stopAnimation(self)
progInd.indeterminate = true
}
progInd.startAnimation(self)
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority,0)) {
self.readData(path) {
dispatch_async(dispatch_get_main_queue()) {
self.window.endSheet(progSheet)
}
}
}
}
func readData(path:String, completion: (() -> Void)) {
print("Hello!?")
completion()
}
Related
My case is utterly simple: I use the next function
private func launchActivity(_ id: String, title: String, invocPhrase: String) {
userActivity = NSUserActivity(activityType: "Open_bank")
userActivity?.title = title
userActivity?.userInfo = ["id": id]
if #available(iOS 12.0, *) {
userActivity?.suggestedInvocationPhrase = invocPhrase
userActivity?.isEligibleForPrediction = true
userActivity?.persistentIdentifier = id
} else {
//Can't actually invoke this block
}
}
to create a certain userActivity, and then add it to Siri, so that it can be invoked by by invocPhrase. Here is the function which does this.
func presentAddOpenBankToSiriVC() {
guard let userActivity = self.userActivity else { return }
if #available(iOS 12.0, *) {
let shortcut = INShortcut(userActivity: userActivity)
let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcut)
viewController.modalPresentationStyle = .formSheet
viewController.delegate = self
present(viewController, animated: true, completion: nil)
} else {
//Can't actually invoke this block
}
}
Later I try to delete it (as well as all other user activities)
NSUserActivity.deleteAllSavedUserActivities {}
And it just does not delete any user activity, contrary to what's written in Apple Documentation
https://developer.apple.com/documentation/sirikit/deleting_donated_shortcuts
Actually, at first, I've tried a method
deleteSavedUserActivities(withPersistentIdentifiers:completionHandler:)
with userActivity's persistentIdentifier, but, obviously, also to no avail.
I've no idea why it refuses to budge but would be grateful to any help or hint
I am integrating TouchBar support to my App. I used the how to from Rey Wenderlich and implemented everything as follows:
If self.touchBarArraygot filled the makeTouchBar() Method returns the NSTouchBar object. If I print out some tests the identifiers object is filled and works.
What not work is that the makeItemForIdentifier method not get triggered. So the items do not get created and the TouchBar is still empty.
Strange behavior: If I add print(touchBar) and a Breakpoint before returning the NSTouchBar object it works and the TouchBar get presented as it should (also the makeItemForIdentifier function gets triggered). Even if it disappears after some seconds... also strange.
#available(OSX 10.12.2, *)
extension ViewController: NSTouchBarDelegate {
override func makeTouchBar() -> NSTouchBar? {
if(self.touchBarArray.count != 0) {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = NSTouchBarCustomizationIdentifier("com.TaskControl.ViewController.WorkspaceBar")
var identifiers: [NSTouchBarItemIdentifier] = []
for (workspaceId, _) in self.touchBarArray {
identifiers.append(NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)"))
}
touchBar.defaultItemIdentifiers = identifiers
touchBar.customizationAllowedItemIdentifiers = identifiers
return touchBar
}
return nil
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
if(self.touchBarArray.count != 0) {
for (workspaceId, data) in self.touchBarArray {
if(identifier == NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)")) {
let saveItem = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: data["name"] as! String, target: self, action: #selector(self.touchBarPressed))
button.bezelColor = NSColor(red:0.35, green:0.61, blue:0.35, alpha:1.00)
saveItem.view = button
return saveItem
}
}
}
return nil
}
}
self.view.window?.makeFirstResponder(self) in viewDidLoad() did solve the problem.
I'm using marmelroy/Zip framework to zip/unzip files in my project, and JGProgressHUD to show the progress of the operation.
I'm able to see the HUD if I try to show it from the ViewDidLoad method, but if I use it in the closure associated to the progress feature of the quickZipFiles method (like in the code sample), the hud is shown just at the end of the operation.
I guess this could be related to a timing issue, but since I'm not too much into completion handlers, closures and GDC (threads, asynchronous tasks, etc.) I would like to ask for a suggestion.
Any ideas?
// In my class properties declaration
var hud = JGProgressHUD(style: .dark)
// In my ViewDidLoad
self.hud.indicatorView = JGProgressHUDPieIndicatorView()
self.hud.backgroundColor = UIColor(white: 0, alpha: 0.7)
// In my method
do {
self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
self.hud.detailTextLabel.text = "0%"
if !(self.hud.isVisible) {
self.hud.show(in: self.view)
}
zipURL = try Zip.quickZipFiles(documentsList, fileName: "documents", progress: { (progress) -> () in
let progressMessage = "\(round(progress*100))%"
print(progressMessage)
self.hud.setProgress(Float(progress), animated: true)
self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
self.hud.detailTextLabel.text = progressMessage
if (progress == 1.0) {
self.hud.dismiss()
}
})
} catch {
print("Error while creating zip...")
}
ZIP Foundation comes with built-in support for progress reporting and cancelation.
So if you can switch ZIP library, this might be a better fit for your project. (Full disclosure: I am the author of this library)
Here's some sample code that shows how you can zip a directory and display operation progress on a JGProgressHUD. I just zip the main bundle's directory here as example.
The ZIP operation is dispatched on a separate thread so that your main thread can update the UI. The progress var is a default Foundation (NS)Progress object which reports changes via KVO.
import UIKit
import ZIPFoundation
import JGProgressHUD
class ViewController: UIViewController {
#IBOutlet weak var progressLabel: UILabel!
var indicator = JGProgressHUD()
var isObservingProgress = false
var progressViewKVOContext = 0
#objc
var progress: Progress?
func startObservingProgress()
{
guard !isObservingProgress else { return }
progress = Progress()
progress?.completedUnitCount = 0
self.indicator.progress = 0.0
self.addObserver(self, forKeyPath: #keyPath(progress.fractionCompleted), options: [.new], context: &progressViewKVOContext)
isObservingProgress = true
}
func stopObservingProgress()
{
guard isObservingProgress else { return }
self.removeObserver(self, forKeyPath: #keyPath(progress.fractionCompleted))
isObservingProgress = false
self.progress = nil
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(progress.fractionCompleted) {
DispatchQueue.main.async {
self.indicator.progress = Float(self.progress?.fractionCompleted ?? 0.0)
if let progressDescription = self.progress?.localizedDescription {
self.progressLabel.text = progressDescription
}
if self.progress?.isFinished == true {
self.progressLabel.text = ""
self.indicator.progress = 0.0
}
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
#IBAction func cancel(_ sender: Any) {
self.progress?.cancel()
}
#IBAction func createFullArchive(_ sender: Any) {
let directoryURL = Bundle.main.bundleURL
let tempArchiveURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString).appendingPathExtension("zip")
self.startObservingProgress()
DispatchQueue.global().async {
try? FileManager.default.zipItem(at: directoryURL, to: tempArchiveURL, progress: self.progress)
self.stopObservingProgress()
}
}
}
Looking at the implementation of the zip library, all of the zipping/unzipping and the calls to the progress handlers are being done on the same thread. The example shown on the home page isn't very good and can't be used as-is if you wish to update the UI with a progress indicator while zipping or unzipping.
The solution is to perform the zipping/unzipping in the background and in the progress block, update the UI on the main queue.
Assuming you are calling your posted code from the main queue (in response to the user performing some action), you should update your code as follows:
// In my class properties declaration
var hud = JGProgressHUD(style: .dark)
// In my ViewDidLoad
self.hud.indicatorView = JGProgressHUDPieIndicatorView()
self.hud.backgroundColor = UIColor(white: 0, alpha: 0.7)
self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
self.hud.detailTextLabel.text = "0%"
if !(self.hud.isVisible) {
self.hud.show(in: self.view)
}
DispatchQueue.global().async {
defer {
DispatchQueue.main.async {
self.hud.dismiss()
}
}
do {
zipURL = try Zip.quickZipFiles(documentsList, fileName: "documents", progress: { (progress) -> () in
DispatchQueue.main.async {
let progressMessage = "\(round(progress*100))%"
print(progressMessage)
self.hud.setProgress(Float(progress), animated: true)
self.hud.textLabel.text = NSLocalizedString("Zipping files...", comment: "Zipping File Message")
self.hud.detailTextLabel.text = progressMessage
}
})
} catch {
print("Error while creating zip...")
}
}
How would i go about checking if a function was called? I have created a function to see if the level was completed like so:
func levelOneCompleted(){
}
When the level one is beat, i call the function levelOneCompleted().
The scene then goes to another scene. It's at this scene that i want to check if the function was called. I am thinking i can make some kind of "if statement".
if levelOneCompleted is called {
//do this
else{
//do this
}
What would be the best way of going about this?
Set a boolean flag to true inside levelOneCompleted():
var isLevelOneCompleted = false
func levelOneCompleted(){
// do things...
isLevelOneCompleted = true
}
And later...
if isLevelOneCompleted {
//do this
} else {
//do this
}
Swift 3 & Xcode 8.3.2
There is 2 trick to do this, here is the code:
// Async operation
func levelOneCompleted(completion: (_ completed: Bool) -> Void) {
// do your function here
completion(true)
}
// Here is how to use it
// than u can declare this in viewDidLoad or viewDidAppear, everywhere you name it
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// this is async operation
levelOneCompleted { (completed) in
if completed {
print("levelOneCompleted is complete")
// do something if levelOneCompleted is complete
DispatchQueue.main.async {
// Update your UI
}
} else {
print("levelOneCompleted is not completee")
// do something if levelOneCompleted is not complete
DispatchQueue.main.async {
// Update your UI or show an alert
}
}
}
}
// Or u can use this code too, and this is Sync operation
var isLevelTwoCompleted: Bool = false
func levelOneCompleted() {
// do your function here
isLevelTwoCompleted = true
}
// to check it u can put this function everywhere you need it
if isLevelTwoCompleted {
//do something if level two is complete
} else {
//do something if level two is not complete
}
I want to run a block of code in 10 seconds from an event, but I want to be able to cancel it so that if something happens before those 10 seconds, the code won't run after 10 seconds have gone by.
I've been using this, but it's not cancellable:
static func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure
)
}
How can I accomplish this?
Swift 3 has DispatchWorkItem:
let task = DispatchWorkItem { print("do something") }
// execute task in 2 seconds
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: task)
// optional: cancel task
task.cancel()
Update for Swift 3.0
Set Perform Selector
perform(#selector(foo), with: nil, afterDelay: 2)
foo method will call after 2 seconds
func foo()
{
//do something
}
To cancel pending method call
NSObject.cancelPreviousPerformRequests(withTarget: self)
Try this (Swift 2.x, see David's answer below for Swift 3):
typealias dispatch_cancelable_closure = (cancel : Bool) -> ()
func delay(time:NSTimeInterval, closure:()->()) -> dispatch_cancelable_closure? {
func dispatch_later(clsr:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(time * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), clsr)
}
var closure:dispatch_block_t? = closure
var cancelableClosure:dispatch_cancelable_closure?
let delayedClosure:dispatch_cancelable_closure = { cancel in
if let clsr = closure {
if (cancel == false) {
dispatch_async(dispatch_get_main_queue(), clsr);
}
}
closure = nil
cancelableClosure = nil
}
cancelableClosure = delayedClosure
dispatch_later {
if let delayedClosure = cancelableClosure {
delayedClosure(cancel: false)
}
}
return cancelableClosure;
}
func cancel_delay(closure:dispatch_cancelable_closure?) {
if closure != nil {
closure!(cancel: true)
}
}
// usage
let retVal = delay(2.0) {
println("Later")
}
delay(1.0) {
cancel_delay(retVal)
}
From Waam's comment here: dispatch_after - GCD in swift?
You need to do this:
class WorkItem {
private var pendingRequestWorkItem: DispatchWorkItem?
func perform(after: TimeInterval, _ block: #escaping VoidBlock) {
// Cancel the current pending item
pendingRequestWorkItem?.cancel()
// Wrap the request in a work item
let requestWorkItem = DispatchWorkItem(block: block)
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + after, execute:
requestWorkItem)
}
}
// Use
lazy var workItem = WorkItem()
private func onMapIdle() {
workItem.perform(after: 1.0) {
self.handlePOIListingSearch()
}
}
References
Link swiftbysundell
Link git
This should work:
var doIt = true
var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: Selector("doSomething"), userInfo: nil, repeats: false)
//you have now 10 seconds to change the doIt variable to false, to not run THE CODE
func doSomething()
{
if(doIt)
{
//THE CODE
}
timer.invalidate()
}
I use #sas 's method in some projects, somehow this doesn't work anymore, maybe something changed after Swift 2.1.1. value copy instead of pointer?
the easiest work around method for me is:
var canceled = false
delay(0.25) {
if !canceled {
doSomething()
}
}
For some reason, NSObject.cancelPreviousPerformRequests(withTarget: self) was not working for me. A work around I thought of was coming up with the max amount of loops I'd allow and then using that Int to control if the function even got called.
I then am able to set the currentLoop value from anywhere else in my code and it stops the loop.
//loopMax = 200
var currentLoop = 0
func loop() {
if currentLoop == 200 {
//do nothing.
} else {
//perform loop.
//keep track of current loop count.
self.currentLoop = self.currentLoop + 1
let deadline = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadline) {
//enter custom loop parameters
print("i looped")
self.loop()
}
}
and then elsewhere in your code you can then
func stopLooping() {
currentLoop = 199
//setting it to 199 allows for one last loop to happen. You can adjust based on the amount of loops you want to be able to do before it just stops. For instance you can set currentLoop to 195 and then implement a fade animation while loop is still happening a bit.
}
It's really quite dynamic actually. For instance you can see if currentLoop == 123456789, and it will run infinitely (pretty much) until you set it to that value somewhere else in your code. Or you can set it to a String() or Bool() even, if your needs are not time based like mine were.