Swift 3 hide NSButton immediately after click - swift

How can I have a button disappear after it's been clicked?
#IBAction func onClick(_ sender: NSButton) {
sender.isHidden = true;
//...a lot of blocking instructions below this line
}
The above works to a certain extent, as the "sender" / button is hidden only after all of the instructions in the function have been processed. I have some blocking IO in the function (socket connections etc.) and I want the button to disappear before all that happens.
I tried using both outlets and sender.

#IBAction func onClick(_ sender: NSButton) {
sender.isHidden = true;
DispatchQueue.main.async {
//...a lot of blocking instructions below this line
}
}

I managed to achieve the desired effect by putting the "blocking" piece of code in the following statement (and pushing the .isHidden setting through immediately, in a synchronous fashion):
DispatchQueue.main.async { /*code*/ }

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.)
}

How to undo UITextField edits in Swift

I'm sure there's a simple answer to this, but I'm pretty noob to programming and have searched extensively here and on the few Undo tutorials I could find.
I have two buttons and a UITextField with a number in it. The buttons increment the number in the textfield up or down by 1 respectively. I have several more sets of these two buttons and a textfield, each representing something different. Then I have undo and redo buttons that undo the changes to the text fields caused by pressing the buttons. This all works great.
So now I'm trying to add the ability to undo changes from editing the text field directly with the number pad, so that it goes into the same undo stack and can be undone and redone from the same undo and redo buttons.
Here's a snippet of the code that controls the undo and redo of the button presses. Then, like I said, I have several more sets of these that all go into the same undo stack. Thanks.
The UITextField
#IBOutlet weak var Money: UITextField!
The Undo and Redo Buttons
#IBAction func Undo(_ sender: UIButton)
{
undoManager?.undo()
}
#IBAction func Redo(_ sender: UIButton)
{
undoManager?.redo()
}
The Up One and Down One Buttons
#IBAction func MonDown(_ sender: UIButton)
{
subtractOneMon(Mon: Money)
}
#IBAction func MonUp(_ sender: UIButton)
{
addOneMon(Mon: Money)
}
The Functions Registering the Undo/Redo
func subtractOneMon(Mon: UITextField)
{
undoManager?.registerUndo(withTarget: self, handler:
{(targetSelf) in
targetSelf.addOneMon(Mon: self.Money)
})
let Mon = Double(Money.text!)
let NewMon = Double(Mon! - 1)
Money.text = Int(NewMon).description
}
func addOneMon(Mon: UITextField)
{
undoManager?.registerUndo(withTarget: self, handler:
{(targetSelf) in
targetSelf.subtractOneMon(Mon: self.Money)
})
let Mon = Double(Money.text!) ?? 0
let NewMon = Double(Mon + 1)
Money.text = Int(NewMon).description
}
You might want to add an "Editing Changed" IBAction for your text field.
This function will be called each time any content of the field is changed.
You'd want to store the previous value of the text field somewhere.
Inside the "Editing Changed" IBAction you'd register an undo operation in your undoManager using the previous value and then update the previous value with the current value.
P.S. There's a potential memory leak in the way your handlers are implemented: currently they are holding a strong reference to your UIViewController. Consider adding [weak self] in the capture list. Check out the Memory Management article by John Sundell to learn more.

iOS 13 Modals - Calling swipe dismissal programmatically

I'd like to detect a modal dismissal in the view controller that's presenting the modal.
This method works amazing for detecting the new iOS 13 swipe dismissal on the new card modals:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MyIdentifier" {
segue.destination.presentationController?.delegate = self
}
}
extension MyController: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
//clean up UI (de-selecting stuff) once modal has been dismissed
}
}
However, presentationControllerDidDismiss is NOT called if the modal dismisses itself programmatically through an action:
#IBAction func btnDismissTap(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Is this a bug or is there a way I can programmatically call whatever the "swipe" dismiss is so I can detect all dismissals the same way? Currently I'm writing extra "dismiss" delegate methods into my modals as a work around and it seems unnecessary.
However, presentationControllerDidDismiss is NOT called if the modal dismisses itself programmatically through an action
self.dismiss(animated: true, completion: nil)
It doesn’t need to be called, because you dismissed the modal yourself, in code. You cannot not know that the modal was dismissed. You don’t need to receive a dismissal signal, because you gave the dismissal signal in the first place.
You typically don’t get a delegate method call reporting something your own code did. Delegate methods report user actions. It would be crazy if everything you yourself did in code came back as a delegate method call.
Mojtaba Hosseini, answer is something I was looking for.
Currently, I need to write a delegate function to let the presenting view know that the user dismissed the modal PLUS do the presentationControllerDidDismiss handler for swipe dismissals:
#IBAction func btnDismissTap(_ sender: Any) {
self.dismiss(animated: true, completion: {
self.delegate?.myModalViewDidDismiss()
})
}
I wanted to handle both of these the same way and Mojtaba's answer works for me. However, presentationControllerDidDismiss does not get invoked if you call it inside of the self.dismiss completion block, you need to call it before.
I adapted my code to use "presentationControllerWillDismiss" (for clarity) and simply called the delegate before I dismiss programmatically in my modals and it works great.
#IBAction func btnDismissTap(_ sender: Any) {
if let pvc = self.presentationController {
pvc.delegate?.presentationControllerWillDismiss?(pvc)
}
self.dismiss(animated: true, completion: nil)
}
Now, I no longer need to create delegate functions to handle modal dismissals in code and my swipe handler takes care of all scenarios.
FYI, what I'm "handling" is doing some UI clean up (de-selections, etc) on the presenting UI once the modal is dismissed.
As #matt mentioned, there is no need to inform the one who dismissed a view by delegate. Because it is already known. BUT if you need that delegate method to be called, you should call it manually your self after dismissing the view:
#IBAction func btnDismissTap(_ sender: Any) {
self.dismiss(animated: true) {
presentationController?.delegate?.presentationControllerDidDismiss?(presentationController!)
}
}

How to detect button highlight on Apple TV in Swift

I am making a simple app that will show the user some information when they hover over a button. I have looked all over for an answer for this but it seems that no one has wondered this yet. I know it is possible to detect button highlights because I have seen it in some apps I downloaded on my Apple TV. Here is basically what I'm aiming for:
#IBAction func firstButton(_ sender: Any) {
//This function would be called when the first button is highlighted/hovered over
label.text = "Hi there"
}
#IBAction func secondButton(_ sender: Any) {
//This function would be called when the second button is highlighted/hovered over
label.text = "How are you?"
}
I know that just creating an IBAction func won't do the trick, but I am just using this as an example of what I want to do.
So is there a way to detect button highlights/button hovering and how?
Thanks in advance. Any help is appreciated.
By hover, I think you mean "is the button in focus". There are a couple ways to tell if a UI element is in focus.
For UIViewController you can override didUpdateFocus which provides context about what happening in the focus engine.
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.nextFocusedView == myButton {
print("My button is about to be focused")
}
else {
print("My button is NOT in focus")
}
}
Or in a custom UIView (ie. a customUIButton) you can check the isFocused property.
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
print(isFocused)
}

open other view with segue only works by pressing button

I want to open an other view controller, after checking if it is the first run of the app.
It works when I press a button but not when I call the method openMap
class TutorialController: UIViewController {
override func viewDidLoad() {
//check if the app opens for the first time
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
openMap()
}
else
{
// This is the first launch ever
UserDefaults.standard.set(true, forKey: "HasLaunchedOnce")
UserDefaults.standard.synchronize()
print("first launch")
openTutorial()
}
}
func openTutorial(){
}
#IBAction func openMap(){
print("openmap opened")
performSegue(withIdentifier: "openMap", sender: nil)
}
}
I assume, you've connected your button to #IBAction func openMap()
if so, you should not call openMap() action inside your viewDidLoad, but use the same code performSegue(withIdentifier: "openMap", sender: nil) instead in your viewDidAppear:
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
performSegue(withIdentifier: "openMap", sender: nil)
}
...
If it doesn't work, you've probably made a mistake with creation of your segue and have connected Button to the destination ViewController directly in your storyboard instead of connecting two controllers:
If so, just remove the old segue, and re-crete it in the way as it is on the image above and assign the same segue id "openMap"
EDITED:
Please, move performing of your segue to the viewDidAppear instead of viewDidLoad, because viewDidLoad is called when the ViewController object is created and it's not yet attached to the window.
Ok, from what I understand is that you want to perform a segue "openMap" when it HasLaunchedOnce. Well what you're doing wrong is that you're calling an #IBAction func. This is my suggestion
if you still want to have that button
create a function and name if whatever you want. Inside this function perform this segue. Link this function to the if else statement and the button.
eg:
//if else statement
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
anotherFunction()
}
//#ibaction (scrap this if you don't want the button)
#IBAction func openMap()
{
print("openmap opened")
anotherFunction()
}
//another function
func anotherFunction()
{
performSegue(withIdentifier: "openMap", sender: nil)
}
hope this helps