Swift: How to disable a button - swift

I'm trying to disable a button for certain amount of time, but have an issue. My procedure is below:
I have 3 buttons and all buttons are enabled
After one button is clicked, disable all buttons. In the meantime, sending data via Bluetooth...
Enable all buttons after finishing sending data.
My goal is to prevent button click when sending data via Bluetooth. I tried to use Button.userInteractionEnabled = false and Button.enabled = false, but it will go to button action handler(The one that I press during data sending period) again whenever I enable button after finishing sending data. Does any one know how to disable buttons permanently for a certain amount of time?

What you have to do is disable the button upon clicked and then somehow enable it when the data transfer it's done.
If this data transfer is called asynchronously, it will probably have a parameter where you can send in a completion block:
button.isUserInteractionEnabled = false
sendData(data) {
success in
button.isUserInteractionEnabled = true
}
If it doesn't accept a completion block as a parameter, it might work in a different way, such as using notifications (firing a notification with a specific name):
button.isUserInteractionEnabled = false
sendData(data)
// adding the observer that will watch for the fired notification
NotificationCenter.default.addObserver(self, selector: #selector(self.didFinishSendingData(_:)), name: Notification.Name(rawValue: "NOTIFICATION NAME GOES HERE"), object: nil)
func didFinishSendingData(_ notification: Notification?) {
button.isUserInteractionEnabled = true
}
We could definitely help more if you post a sample of your code.

Why can you achieve this on the main thread with an activityIndicator like below:
let activityIndicator = UIActivityIndicatorView()
activityIndicator.frame = view.frame
activityIndicator.center = view.center
activityIndicator.activityIndicatorViewStyle = .gray
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
//start activity indicator
activityIndicator.startAnimating()
//send your data via bluetooth on main thread
DispatchQueue.main.async {
//put your sending via bluetooth code here with a completion handler when completes
//then in the completion handler, put below line
activityIndicator.stopAnimating()
}

Related

How to update buttons status when the user reopens the app? (Swift 5)

I have a question about saving the last status of the buttons for a "Hung Person" game. By using UserDefaults I was able to save almost all the status of the user when the game was closed (Screen1 shows the user's game status while playing), as you can see, the user chose 2 wrong letters that were not in the hidden word (M and N, which are in red and disabled) and the user correctly guessed the O, B, C letters (which disappeared from the alphabet and also are disabled).
When the user closes the app and when it is reopened again, I was able to reload almost all the previous data of the last session played, except for the buttons status. You can see this in Screen2. I used UserDefaults to save and load the game. I save the status of the game every time the user taps on any button (trying to guess the hidden word's characters), and I load the data back in viewDidLoad().
The game is made programmatically, I created the buttons using something like the following:
for row in 0..<5 {
for column in 0..<6 {
if counterLetter < 26 {
let letterButton = UIButton(type: .system)
letterButton.titleLabel?.font = UIFont.systemFont(ofSize: titleLabelFontSize)
letterButton.setTitle(englishAlphabet[counterLetter], for: .normal)
letterButton.isHidden = false
letterButton.isEnabled = true
letterButton.alpha = 1
letterButton.setTitleColor(.red, for: .disabled)
letterButton.addTarget(self, action: #selector(letterTapped), for: .touchUpInside)
letterButton.layer.borderWidth = 1
letterButton.layer.borderColor = UIColor.black.cgColor
// Button's frame creation
let frame = CGRect(x: column*widthButton, y: row*heightButton,
width: widthButton, height: heightButton)
letterButton.frame = frame
buttonsView.addSubview(letterButton)
letterButtons.append(letterButton)
counterLetter += 1
} else {
continue
}
}
}
It is in the letterTapped method (inside the #selector(letterTapped) part in the previous code) where I save the user's progress when any alphabet button is tapped. As I said before, I recall the player's last status in viewDidLoad().
I tried to save the buttons status inside the letterTapped method as well, but I haven't been able to save or reload the buttons status as the player's had.
Can you give me please a hand about where I have to use userDefaults to save the last session status of the alphabet buttons, please? So that when the players reopens the app, the screen2 is the same as screen1.
If you need me to share my code, I can do willingly it.
Thanks in advance.
Regards!
What you are looking for is a K.V.O called "UIApplicationWillEnterForeground" and "UIApplicationDidEnterBackground"
In your viewDidLoad of your ViewController you should do the following:
func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: Notification.Name.UIApplicationDidEnterBackground, object: nil)
then implement these function in the same ViewController:
func appMovedToForeground() {
print("Application moved to Foreground!")
}
func appMovedToBackground() {
print("Application moved to Background!")
}
now when your app goes to the background or returns to the foreground those functions will get called

Pressing on app notification always activates app window

My application schedules notification and displays it well
NSUserNotificationCenter.default.delegate = self
let notification = NSUserNotification()
notification.deliveryDate = Date(timeIntervalSinceNow: TimeInterval(10))
notification.title = title
notification.actionButtonTitle = "Do something"
notification.soundName = notificationSound != "none" ? notificationSound : nil
notification.otherButtonTitle = "Close"
NSUserNotificationCenter.default.scheduleNotification(notification)
When I press on the button activation method is called well
func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
DDLogDebug("Did activate notification")
switch notification.activationType {
...
}
The problem is that opened window is activated after pressing on it that I don't need.
Application is agent (UIElement) with multiple windows opened. Tried non-agent application, the problem still persists - it activates main window
Any ideas?
UPDATE
Unfortunately, I didn't find the answer. I implemented my own mechanism of notifications that displays custom Panel

How to set a Timer schedule on appDelegate method?

I need to have my app fetch data and show an alert to user depending on the response, I'm trying to create a function and then call it on appDelegate class...
the function:
func triggerPushMessages(){
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { (time) in
// I want to perform a request here to show the alert to user
let content = UNMutableNotificationContent()
content.title = "testNotifications on background state"
content.body = "date of notification: \(Date().timeIntervalSinceNow)"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let notificationRequest = UNNotificationRequest(identifier: "test", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notificationRequest, withCompletionHandler: { (err) in
if let error = err {print(error);return}
})
}
}
I created this function to test if notifications would show up after I set the timer but what happens is that when I set timer.schedule function pushNotification doesn't work if I remove timer pushNotification works...
the thing is, I need to request data from an Api first wait for the response and then show the push notification to alert the user...
how can I get on with this?
I call this method on:
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
self.triggerPushMessages()
}
Is it even possible? how apps like whatsapp, telegram, tinder, can handle/ fetch data on background state and then show notifications to user?
thank you in advance for the answer.
I'd like to add that my 60 seconds request are just for test purposes I'll perform the request once an hour...
You cannot add 60 seconds timer in the app background state. its not allowed in iOS. If you want to show notification to the user even if the app is in the background/not, you can send push notifications from your backend server by integrating the APNS(Apple push notifications). because you are saying that you want to show notification to user, once the api response comes. In this case server will have data and It can decide and send notification to the User.

Check if NSAlert is currently showing up

I am using an NSAlert to display error messages on the main screen of my app.
Basically, the NSAlert is a property of my main view controller
class ViewController: NSViewController {
var alert: NSAlert?
...
}
And when I receive some notifications, I display some messages
func operationDidFail(notification: NSNotification)
{
dispatch_async(dispatch_get_main_queue(), {
self.alert = NSAlert()
self.alert.messageText = "Operation failed"
alert.runModal();
})
}
Now, if I get several notifications, the alert shows up for every notification. I mean, it shows up with the first message, I click on "Ok", it disappears and then shows up again with the second message etc... Which is a normal behaviour.
What I would like to achieve is to avoid this sequence of error message. I actually only care about the first one.
Is there a way to know if my alert view is currently being displayed ?
Something like alert.isVisible as on iOS's UIAlertView ?
From your code, I suspect that notification is triggered in background thread. In this case, any checks that alert is visible right now will not help. Your code will not start subsequent block execution until first block will finish, because runModal method will block, running NSRunLoop in modal mode.
To fix your problem, you can introduce atomic bool property and check it before dispatch_async.
Objective-C solution:
- (void)operationDidFail:(NSNotification *)note {
if (!self.alertDispatched) {
self.alertDispatched = YES;
dispatch_async(dispatch_get_main_queue(), ^{
self.alert = [NSAlert new];
self.alert.messageText = #"Operation failed";
[self.alert runModal];
self.alertDispatched = NO;
});
}
}
Same code using Swift:
func operationDidFail(notification: NSNotification)
{
if !self.alertDispatched {
self.alertDispatched = true
dispatch_async(dispatch_get_main_queue(), {
self.alert = NSAlert()
self.alert.messageText = "Operation failed"
self.alert.runModal();
self.alertDispatched = false
})
}
}
Instead of run modal you could try
- beginSheetModalForWindow:completionHandler:
source: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSAlert_Class/#//apple_ref/occ/instm/NSAlert/beginSheetModalForWindow:completionHandler:
In the completion handler set the alert property to nil.
And only show the alert if the alert property is nil ( which would be every first time after dismissing the alert).
EDIT : I don't see the documentation say anything about any kind of flag you look for.

Swift - Implement global activity indicator

I'm currently attempting to make a global activity indicator in swift, which is called whenever a fetch to the api is made. The idea is that the activity indicator will appear in the top left of a navigation bar (navigation view controller's child), and is available on every app page.
Can't show example image due to new account / low rep
I have the activity indicator displaying correctly, I'm just not sure on how to make it available from any page on the app - i've considered an extension, but am not sure on what the best way to approach it is. Any help would be greatly appreciated.
Activity Indicator Code:
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.White)
var activityItem = UIBarButtonItem()
func navBarActivity() {
// Call navBarActivity() to start activity indicator
// Use navigationItem.leftBarButtonItem = nil to stop activity indicator
activityIndicator.startAnimating()
activityIndicator.hidden = false
self.activityItem = UIBarButtonItem(customView: activityIndicator)
self.navigationItem.leftBarButtonItem = activityItem
}
I would recommend extension like this.
extension UIViewController {
func displayNavBarActivity() {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .White)
indicator.startAnimating()
let item = UIBarButtonItem(customView: indicator)
self.navigationItem.leftBarButtonItem = item
}
func dismissNavBarActivity() {
self.navigationItem.leftBarButtonItem = nil
}
}
Call self.displayNavBarActivity() before api call, and call self.dismissNavBarActivity() after api call done.
However, I want you to check networkActivityIndicatorVisible of UIApplication. Consider using this option.
Specify YES if the app should show network activity and NO if it should not. The default value is NO. A spinning indicator in the status bar shows network activity. The app may explicitly hide or show this indicator.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/#//apple_ref/occ/instp/UIApplication/networkActivityIndicatorVisible