How to handle modifier keys in Cocoa menubar application - swift

So I have a menu bar application that has a slider inside a custom view: I want the ability to press the option key while dragging to make the slider snap to increments.
Right now, everything is working... except I cannot find a single way to notify my menu app that the option key is being pressed. Here's what I've tried:
NSEvent.addGlobalMonitorForEvents
"Note that your handler will not be called for events that are sent to your own application."
NSEvent.addLocalMonitorForEvents
"Your handler will not be called for events that are consumed by nested event-tracking loops such as control tracking, menu tracking, or window dragging"
NSApplication.shared.currentEvent
This dosesn't work because the flagsChanged event isn't handled by the app, so this value is set to another value (mouse button presses).
flagsChanged(with event: NSEvent)
This doesn't get called. I've set the viewController to acceptFirstResponder as well as resigned the first responder of the view. Also, I've subclassed the view and overridden flagsChanged and that hasn't worked.
I've tried creating a menu item that has a key like "s" or "p" to trigger an action. But when the "action" gets triggered, the menu bar hides...
So I'm trying to find how I can be notified when the option key is pressed. Is this possible? I can clarify if you have any questions.

In your custom slider view's mouseDragged method, you'd simply check the NSEvent parameter's modifierFlags to see if it contains the option key.
--
Since it could be interpreted that the slider is an actual NSSlider, if that's the case then the slider's control is really implemented in with an NSSliderCell, and the mouse tracking gets handed off from the view to the cell within mouseDown, so the NSSlider's mouseDragged wouldn't be called, and instead you'd have to do work in the cell's trackMouse:inRect:... method. But if you're using an NSSlider, then you'd probably simply want to use altIncrementValue.

Related

Update NSTouchBar on the fly to add/remove items programmatically

I'm currently implementing the NSTouchBar api to my macOS application.
At this moment, the only touch bar I have has the main View Controller as its delegate and I can add items to it fine. The catch is, I need some of those items to appear only when a certain condition is met (a row is selected in a table).
Consider I have a boolean that indicates whether or not the button should be visible. How do I update the NSTouchBar on the fly to show/hide this button in case my boolean changes? (I don't need to observe this boolean, I could simply make the call to update in another method I already implemented)
What I did for now is the following: in touchBar(:makeItemForIdentifier), I have a switch for all identifiers, and under the proper case, I either return the NSCustomTouchBarItem with the button, or nil if my boolean is false.
I tried calling makeTouchBar again after a row of the table is selected but it doesn't update the buttons' visibility, as if touchBar(:makeItemForIdentifier) is not called again.
Thanks!
Four ideas:
Try changing your touch bar's defaultItemIdentifiers to the set of item identifiers that should be shown. Note that this would be problematic if the user has customized the touch bar, but I think swapping items on-demand and customizing the touch bar doesn't go well together anyway. This also has the advantage that you don't need to return nil in touchBar(:makeItemForIdentifier:).
Calling makeTouchBar() will create a new NSTouchBar instance, but not change the touchBar property. Try something like
viewController.touchBar = viewController.makeTouchBar()
or
viewController.touchBar = nil
Set the touchBar property on the NSTableRowView that should show extra items when selected, and make sure to include the otherItemsProxy in your defaultItemIdentifiers. As the contents of the touch bar are comprised of all elements in the responder chain, this might include the touchBar property of the table row (provided it can become first responder).
Are you sure that these items should be hidden when the row is not selected? Consider disabling them instead (e.g. by setting the enabled property of the buttons they contain to false).
Just invalidate the touchbar in your view controller:
self.touchbar = nil
The delegate method makeTouchBar() will then automatically be called. Using your flags, you can easily choose the icons to appear.
EDIT: this solution has been tested and works fine.

Hide keyboard plus return key?

I have a view with a textfield at the top, a textview under it and two buttons below the textview.
The keyboard is configured with a "Done" button. Once the user has typed in their info, they click the save button, which is below the textview. First they click Done to hide the keyboard (and reveal the save button) then click the save button.
I need to allow carriage returns in the textview but "Return" is already taken up by Done.
How is it usually handled when you need a Return key and ability to also hide the keyboard?
Drag a button into your view, delete the text, and resize it to take up the entire view. In the document outline, select the new button and drag it to the top of the list of elements. This puts it in the background so it is not hiding the elements of your view.
Add this code to your ViewController:
#IBAction func hideKeyboard(sender: AnyObject) {
self.textField.resignFirstResponder()
}
Link the button to this action and you're all set.
If you're using a UINavigationBar or have other buttons or fields, activating any of those UIControls could be detected and used to dismiss the keyboard via resignFirstResponder(). In fact Save/Cancel/Done are UIBarButtonItems and are a standard mechanism for completing things and changing state, and create a framework for accomplishing what you want. If you don't take that approach then you have to get creative with how you do it, and also make it clear to the user what needs to be done.
In Interface Builder you can change the type of your main view from UIView to UIControl and then use addTarget() to detect touch events as a 'touch outside' area and use those actions to resign first responder as well. But you might want to consider a UINavigationBar or some other button bar or tab interface to make state transitions.
Also review iOS Human Interface Guidelines document. It's a great document for understanding how iOS is designed to handle common situations like what you are dealing with, and it can get you out of design ruts. It's well written and worth re-visiting periodically.

MKMapKit Follow User

How can I have my MKMapView follow my user around until they scroll, and then have a button to follow the user around again?
Here is the flow I would like it to have.
View Loads:
Zoom in and center on the users current location, then follow the user around.
User scrolls:
Do nothing until a button is pressed
Button pressed:
Same code as 'View Loads'
Your location manager is continuously providing you with new location information via the delegate method locationManager:didUpdateToLocation:fromLocation:. Change the map's region whenever you get an update. Before doing so, check a flag ("shouldFollowCurrentLocation" or similar) that is set by default. You will unset the flag when your map view delegate gets mapView:regionWillChangeAnimated: (you will of course have to keep track of the occasions when you cause the region to change programmatically) and reset it in the button's action method.

Custom MKAnnotationView - How to capture touches and NOT dismiss the callout?

I have a custom MKAnnotationView subclass. It is showing the view exactly as I want it to. In that view, I have a button. I want to capture events on the button to perform an action. This works just fine.
However, I do NOT want the callout to be dismissed or disappear. Basically, touching the button in the callout will start playing a sound, but I want to leave the annotation up so the user can press stop if they want to, without having to touch the map pin again to bring the annotation back up. In another instance, I want the button touch to animate more details in the callout, so I definitely don't want to dismiss the callout at that point.
How can I keep the callout from disappearing whenever the user selects the callout or a button inside the callout?
This may not be the best solution, but it definitely works. First off, I tried a number of things, like observing for context and such, but I never got past crashing, and it seemed cumbersome. So, this is what I did:
I first specified what the controlling factor was for keeping an alert viewable. In my case, I created a custom annotation view, and whenever the user clicks a button on that custom view, I want it to stay visible, and maybe even change the content. So, I set a delegate on that custom view so that my map can know when something changes. In my map view controller, I catch that message and set a class member variable to true to signify that I want the annotation view to stay.
NOTE: This will happen before the selection messages occur.
Now, in my didDeselectAnnotation method, I check the boolean value. If I want to keep it visible, I opt to NOT remove my annotation, I reset the boolean value, and I re-select the annotation manually, setting animation to NO. This lets the annotation view "stay" visible--maybe a cheat, but the user can't see the difference. Whenever that boolean value says that deselection is ok, I simply remove the annotation and all is well.
So, the workflow is this:
Touch pin
CustomAnnotationView is displayed
User clicks a button on CustomAnnotationView, which notifies the delegate (mapView) that the
action occurred
Set the class boolean value to know that you want to keep the annotation around
mapView then calls didDeselectAnnotation method
In didDeselectAnnotation method, use conditional to decide if you should remove the annotation, or keep it around by not removing the annotation and manually re-selecting it without animation.
I hope this helps others. It took me a while to figure this out, so I hope it saves you time.
If you find a better solution, by all means, please post it here!
you can suppress callout closing by using hittest, check this following post.
Detect tap on title of callout

UIPickerView - Selects row too fast

I am currently using a UIPIckerView in my app to allow a user to select from a list of options. The problem is that there isn't enough of a delay when the user stops spinning the wheel and it is selecting a value before the user has a chance to scroll further down the list.
Is there a way to override the default behavior that selects the row as soon as the wheel stops spinning and the user removes their finger? I see Mobile Safari includes a "Done" button which would be great.
I can provide code if necessary (not sure how it would help).
Thanks!
You can add this manually; just add a done button to the view that holds the UIPicker, and have IT do whatever action you're currently performing in – pickerView:didSelectRow:inComponent:.
The UIPickerView automatically selects which ever row stops in the center. It does not work like a table but more like a popup menu. As such, you can't use a picker view like a button to call an action because it will trigger the moment the user stops moving it whether that represents their final choice or not.
Instead, as noted previously, you need a second control element (usually a button) to call the action that makes use of the pickerview's selection.