How to check notification permissions on the main thread in swift? - swift

I've got a function in my code to check if we can have permission to display a UILocalNotification.
open func hasPermission() -> Bool {
if let permissions = UIApplication.shared.currentUserNotificationSettings {
if permissions.types.contains(.alert) {
return true
}
}
return false
}
This code has triggered a warning in Xcode 9:
UI API called from background thread:
UIApplication.currentUserNotificationSettings must be used from main
thread only
How do I fix this? I know there is the DispatchQueue.main.async method, just not sure how to implemented that.

You can do it like this.
DispatchQueue.main.async {
if hasPermission() {
//
}
}

Related

change UIVIew visibility during a long func call

I have a search page with uses OAuth to make an external call to a website for data. Sometimes the data is very quick and others quite long. So I have created a custom object (Searching) to display on screen to indicate that a search is happening (the custom object is just 2 UIImageViews in a UIView)
The problem is that the searching.isHidden = false doesn't actually happen until the end of the func which happens after it gets all the data, even though it is called first. Which is obviously too late.
I tried moving the isHidden to a background thread but get an error saying UIView calls must be on the main thread
I tried moving the display call to its own func with an #escaping callback and then run the search after it returns but it still does not update.
If I remove the search() line it displays properly.
I've also tried forcing a refresh on the object using
self.view.setNeedsLayout()
self.view.setNeedsDisplay()
and it didn't work
class Search {
override func viewDidLoad() {
super.viewDidLoad()
searching.isHidden = true
}
#IBAction func search(_ sender: Any) {
if self.searching.isHidden == false {
self.searching.isHidden = true
}
else {
self.searching.isHidden = false
}
self.view.setNeedsLayout()
self.view.setNeedsDisplay()
//I've also tried using an escaping func to call the isHidden and call back when complete
//self.searching.show() {
//self.view.setNeedsLayout()
//self.view.setNeedsDisplay()
//self.search()
//}
//I've tried an async call
// DispatchQueue.main.async {
// self.search()
// }
}
func search() {
keywordText.resignFirstResponder()
//perform OAuth external search
if results.count > 1 {
searching.isHidden = true
self.performSegue(withIdentifier: "results", sender: nil)
}
return
}
}
On iOS (and MacOS) UI updates don’t get displayed until your code returns and the event loop gets a chance to run. Code that makes UI changes and then immediately does a long-running task on the main thread will not see those changes on-screen until the long-running task completes.
One way to handle this is to change you UI and then use dispatchAsync to trigger the long-running task:
searching.isHidden = false
DispatchQueue.main.async {
// Put your long-running code here.)
}

SWIFT 5: Increment Value Async of Determinate Circular Progress Indicator

I searched a lot but I can't find a good explanation of using a determinate circular progress indicator.
I have a Helper-Class that converts a PDF to an image. I have more than 400 pages so I decided to make these things asynchron with the following code:
DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
… do the work …
view.incrementProgressIndicator()
}
Insight the view, where the ProgressIndicator lives, the called function looks the following:
func incrementProgressIndicator() {
self.progressIndicator.increment(by: 1)
}
For sure, Swift says: "UI API called on a background thread".
I tried to surround the code of incrementing with:
DispatchQueue.main.async {
self.progressIndicator.increment(by: 1)
}
But now, all the work of indicating is done after the conversion of PDF is completed.
Can someone tell my how this can be done? I can't find a working tutorial for my issue.
Thank you for your help!
I found the solution for my issue. I replace the
DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
… do the work …
view.incrementProgressIndicator()
}
with the following code:
for i in 0..<pdfDocument.numberOfPages {
DispatchQueue.global(qos: .background).async {
let pdfPage = pdfDocument.page(at: i + 1)!
if let result = self.convertPageToImage(withPdfPage: pdfPage, sourceURL: sourceURL, destUrl: destinationURL, index: i) {
urls[i] = result
}
DispatchQueue.main.async {
if let delegate = self.delegate {
print("Ask for Increment")
delegate.incrementProgressIndicator()
}
}
}
}

Swift - Exit function with background thread inside

I have a function that does some looping in the background and update the UI:
func doSomething() {
DispatchQueue.global(qos: .userInteractive).async {
for ... {
if ... {
...
DispatchQueue.main.async {
//Update UI
...
if ... {
// Show UIAlert
//Exit function
}
}
}
}
}
}
I want to exit the function (hence cancelling the background thread). If I use return, the alert shows up but the background thread keeps looping data to the end. I think the reason is that when swapping to the main thread, I am out of scope of the function.
I am new to Swift multi-threading, so any Idea?
As far as I know if you want to stop execution of for loop which is running on background thread, then you have to stop it from background thread itself which you are trying to stop from main thread block of code. try something line below :
func doSomething() {
var continueForLoop = true
DispatchQueue.global(qos: .userInteractive).async {
for ... {
if ... && continueForLoop{
...
DispatchQueue.main.sync {
//Update UI
...
if ... {
// Show UIAlert
//Exit function
continueForLoop = false
}
}
}else{
break;
}
}
}
}
P.S. to understand multithreading in iOS go thought this link : iOS Concurrency Note: try to understand the multithreading part only as code contains old swift syntaxes.

Check to see if function was called?

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
}

Launch Main app from MenuBar app

I've managed to create an Application and I don't want it to be on the Dock until a user clicks the MenuBar Item, then it launches the app and if a user quits the Main app, the menubar Item still remains.
Ok after days of research, I finally figured how to do it.
Swift 2.3
func toggleApp(sender: AnyObject) {
if self.window!.visible {
self.window.orderOut(window)
NSApplication.sharedApplication().setActivationPolicy(NSApplicationActivationPolicy.Accessory)
} else {
NSApplication.sharedApplication().setActivationPolicy(NSApplicationActivationPolicy.Regular)
self.window!.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
}
}
And this is for keeping the Menubar when the user quits(CMD Q) the app:
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
// Cancel terminate if pref set
self.window.close()
NSApplication.sharedApplication().setActivationPolicy(NSApplicationActivationPolicy.Accessory)
return NSApplicationTerminateReply.TerminateCancel
}
Hope it helps someone.
in Swift 5:
func toggleApp(sender: AnyObject) {
if self.window.visible {
self.window.orderOut(window)
NSApplication.shared.setActivationPolicy(NSApplication.ActivationPolicy.accessory)
} else {
NSApplication.shared.setActivationPolicy(NSApplication.ActivationPolicy.regular)
self.window!.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
}