I have a method with a completion that returns a bool. But I cant figure out how to get around this error.
Error
UISwitch.isOn must be used from main thread only
Button Action
#IBAction func notificationSwitch(_ sender: Any) {
LocalNotification().checkEnabled(completion: { (success) -> Void in
// When download completes,control flow goes here.
if success == false{
print("Cant Turn On")
self.notificationToggle.isOn = false
} else {
print("Can Turn On")
if self.notificationToggle.isOn == true {
self.notificationToggle.isOn = false
} else {
self.notificationToggle.isOn = true
}
}
})
}
also already tried wrapping the LocalNotifications().... in DispatchQueue.main.async but still get the same error
You're almost there. It is not the checkEnabled that needs to be wrapped in the call to get onto the main thread, but the stuff "inside" it:
LocalNotification().checkEnabled(completion: { (success) -> Void in
DispatchQueue.main.async {
if success == false {
Related
In my app I want to ask for camera access when the user pressed a camera button, which would take him to an AVFoundation based camera live preview.
That view is presented using a "present modally segue".
So in my ViewController I currently override the shouldPerformSegue() and return true if the user gives permission or has granted it already, otherwise false.
If the user didn't grant access I am showing an Alert in which he can go to settings to change the permission. That is done in showPermissionInfo().
My problem is, that AVCaptureDevice.requestAccess is called asynchronously and thus hasCameraPermission is not set to true before I'm checking for it.
Is there a way to call these restriction accesses in a blocking way?
Thank you!
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "Modally_ToCameraViewController"
{
var hasCameraPermission = false
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized
{
hasCameraPermission = true
} else {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
hasCameraPermission = true
} else {
hasCameraPermission = false
}
})
}
if(!hasCameraPermission){
showPermissionInfo()
}
return hasCameraPermission
}
return true
}
One easy solution would be to create a semaphore and wait on it until the completion closure is called. semaphore.wait will block the current thread until semaphore.signal is called.
let semaphore = DispatchSemaphore(value: 0)
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
hasCameraPermission = true
} else {
hasCameraPermission = false
}
semaphore.signal()
})
semaphore.wait()
This happens when i am saving a video, I am also trying to segue to another view controller. I am not sure why the app crashes here
func finalExportCompletion(_ session: AVAssetExportSession) {
PhotoManager().saveVideoToUserLibrary(fileUrl: session.outputURL!) { (success, error) in
if success {
ProgressHUD.showSuccess("Video Saved", interaction: true)
self.finalVideo = session.outputURL!
//FileManager.default.clearTmpDirectory()
self.clipsCollectionView.reloadData()
} else {
ProgressHUD.show(error?.localizedDescription)
}
self.performSegue(withIdentifier: "toPostVideoViewController", sender: nil)
}
}
Your crash is issued to modifications in UI while your are in background thread, as said your crash log, and #rmaddy in his comment, THREAD 8 is a background thread, your need execute all your UI actions in Thread 1 which is the "Main Thread" you have to modify your code like this
func finalExportCompletion(_ session: AVAssetExportSession) {
PhotoManager().saveVideoToUserLibrary(fileUrl: session.outputURL!) { (success, error) in
DispatchQueue.main.async{
if success {
ProgressHUD.showSuccess("Video Saved", interaction: true)
self.finalVideo = session.outputURL!
//FileManager.default.clearTmpDirectory()
self.clipsCollectionView.reloadData()
} else {
ProgressHUD.show(error?.localizedDescription)
}
//I don't know if you anyway want to go to "toPostVideoViewController" this you need to do it also in main thread
self.performSegue(withIdentifier: "toPostVideoViewController", sender: nil)
}
}
}
I'm trying to save my data to plist file using Operation.
I want to implement an asynchronous saving, so I've override the start()
But when I try to check wether saving have finished isFinished remains false.
However data has saved successfully, as I planned
class OperationDataManager: Operation {
var user: AppUser?
override func start() {
if let plist = Plist(name: "userFile") {
if let dict = plist.getMutablePlistFile() {
dict["userName"] = user?.userName
dict["userInfo"] = user?.userDescription
dict["userColor"] = NSKeyedArchiver.archivedData(withRootObject: user?.userColor)
dict["userImage"] = UIImagePNGRepresentation((user?.userImage)!)
do {
try plist.addValuesToPlistFile(dictionary: dict)
} catch {
print(error)
}
}
}
if isFinished == true {
print("Operation: finished")
} else {
print("Operation: not finished")
}
}
}
What's wrong?
isFinished isn't set to true until after main completes. It will never be true inside start or main.
There's no need for your operation to check isFinished. It is finished when main gets to the end.
I'm having an issue with changing view controllers in local authentication. When all the code executes in the success if statement the view controller does not change even though I'm telling it to. I've tried everything that I know but nothing works. Here is my local authentication code.
let authentication = LAContext()
var authenticationError: NSError?
authentication.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &authenticationError)
if (authenticationError != nil) {
// Authentication Not available for this version of iOS
self.gotoMainViewController()
} else {
authentication.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "Access Passy using Touch ID") {
(success, error) in
if (error != nil) {
// There was an error - user likley pressed cancel
print(error?.localizedDescription)
} else {
if (success) {
dispatch_async(dispatch_get_main_queue()) {
self.gotoMainViewController()
}
} else {
self.showFailedTouchIDError.showAlert()
}
}
}
}
Here is the gotoMainViewController() code.
func gotoMainViewController() {
let viewController = MainViewController()
self.navigationController?.pushViewController(viewController, animated: true)
}
I figured it out!! It seems that local authentication needs a boolean IF statement to be wrapped around all of the code. I'm not sure if this is true...but it worked for me.
I've been getting an EXC_BAD_ACCESS error/crash when I engage in a specific action within my application. Figuring that this was a memory management issue, I enabled NSZombies to help me decipher the issue. Upon the crash, my console gave me the following message:
heres my stack trace:
and the new error highlighting my app delegate line:
Now being the debugger is referring to a UIActivityIndicatorRelease, and the only line of code highlighted in my stack trace is the 1st line in my delegate, is there an issue with my Activity Indicator UI Element? Here is the logic within my login action ( which forces the crash every time ):
#IBAction func Login(sender: AnyObject) {
activityIND.hidden = false
activityIND.startAnimating()
var userName = usernameText.text
var passWord = passwordText.text
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
} else {
self.activityIND.stopAnimating()
if let message: AnyObject = error!.userInfo!["error"] {
self.message.text = "\(message)"
}
}
}
}
is there an error within it?
All your code that manipulates UI objects absolutely, positively must be done from the main thread. (and so it should be in a call to dispatch_async(dispatch_get_main_queue()) as #JAL says in his comment.
That includes not just the self.activityIND.stopAnimating() line, but the code that sets label text as well (any code that manipulates a UIKit object like a UIView).
Your if...else clause should look something like this:
if user != nil
{
dispatch_async(dispatch_get_main_queue())
{
self.activityIND.stopAnimating()
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
else
{
dispatch_async(dispatch_get_main_queue())
{
self.activityIND.stopAnimating()
if let message: AnyObject = error!.userInfo!["error"]
{
self.message.text = "\(message)"
}
}
}
So it turns out, in my viewDidLoad() I had the following code in attempt to hide the indicator on the load:
UIActivityIndicator.appearance().hidden = true
UIActivityIndicatorView.appearance().hidesWhenStopped = true
not knowing this would deallocate the indicator for the remainder of the application so when i called the following in my login logic:
activityIND.hidden = false
activityIND.startAnimating()
i was sending a message to an instance that was no longer available, causing the crashes. So all i did was adjust my code in viewDidLoad()
to :
activityIND.hidden = true
activityIND.hidesWhenStopped = true
using the name of the specific outlet I created rather than the general UIActivityIndicatorView
All UI related operation should execute in Main Thread, i.e., within
dispatch_async(dispatch_get_main_queue(){}
block
#IBAction func Login(sender: AnyObject) {
activityIND.hidden = false
activityIND.startAnimating()
var userName = usernameText.text
var passWord = passwordText.text
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self) //UI task
}
}
else {
dispatch_async(dispatch_get_main_queue())
{
self.activityIND.stopAnimating() //UI task
if let message: AnyObject = error!.userInfo!["error"]
{
self.message.text = "\(message)" //UI task
}
};
}
}
}
Refer some good articles on Concurrency here and here