I have a UIMenu on my app and I want to detect when a user taps outside(dismiss) the UIMenu. But it seems like Apple does not support any delegates by default for this action.
Because when a user taps outside I want to change the image of the button.
Sample Image
One rather hacky way I found, is to subclass UIButton and override contextMenuInteractionWillEndFor.
class MyButton: UIButton {
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
super.contextMenuInteraction(interaction, willEndFor: configuration, animator: animator)
print("ending!")
}
}
contextMenuInteractionWillEndFor is the delegate method that is called when you dismiss a UIMenu, but if you are setting the button's menu property, the button itself will become the delegate of the UIContextMenuInteraction, and you can't set it to something else, which is why you have to subclass UIButton.
Compare this to when you are adding a context menu using addInteraction, where you have control over the UIContextMenuInteractionDelegate.
Related
I m creating a custom NSView, it has NSBUTTON of type radio and NSTextfield. Right now, it doesnot show property of radio button i.e. if I select one radio button, other radio buttons should go off. Any fix
the layer of views is like this
NSCUSTOMBUTTON has NSTEXTFIELD AND NSBUTTON, THEY are then added to the stackview, which is further added to the super view.
Edit: I found the answer, basically you have to implement your own delegate function, that handles mousedown event and then add the functionality of radio button where you extend the delegate.
private func handleClickAction(_ clickedButton: ChoiceButton) {
guard isRadioGroup else { return }
if previousButton?.value != clickedButton.value {
previousButton?.state = .off
previousButton = clickedButton
}
}
Description
I am trying to use NSSegmentedControls to transition between Child ViewControllers. The ParentViewController is located in Main.storyboard and the ChildViewControllers are located in Assistant.storyboard. Each ChildViewController has a SegmentedControl divided into 2 Segments and their primary use is to navigate between the ChildViewControllers. So they are set up as momentaryPushIn rather than selectOne. Each ChildViewController uses a Delegate to communicate with the ParentViewController.
So in the ParentViewController I added the ChildViewControllers as following:
/// The View of the ParentViewController configured as NSVisualEffectView
#IBOutlet var visualEffectView: NSVisualEffectView!
var assistantChilds: [NSViewController] {
get { return [NSViewController]() }
set(newValue) {
for child in newValue { self.addChild(child) }
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
addAssistantViewControllersToChildrenArray()
}
override func viewWillAppear() {
visualEffectView.addSubview(self.children[0].view)
self.children[0].view.frame = self.view.bounds
}
private func addAssistantViewControllersToChildrenArray() -> Void {
let storyboard = NSStoryboard.init(name: "Assistant", bundle: nil)
let exampleChild = storyboard.instantiateController(withIdentifier: "ExampleChild") as! ExampleChildViewController
let exampleSibling = storyboard.instantiateController(withIdentifier: "ExampleSibling") as! ExampleSiblingViewController
exampleChild.navigationDelegate = self
exampleSibling.navigationDelegate = self
assistantChilds = [exampleChild, exampleSibling]
}
So far so good. The ExampleChildViewController has an NSTextField instance. While I am in the scope of the TextField, I can trigger the action of the SegmentedControls. Its navigating forward and backward as it should. But once I leave the scope of the TextField I can still click the Segments, but they are not triggering any action. They should be able to navigate forward and backward even if the TextField is not the current "First Responder" of the application. I think I am missing something out here, I hope anyone can help me with this. I know the problem is not the NSSegmentedControl because I am seeing the same behavior with an NSButton, which is configured as Switch/Checkbox, in the SiblingViewController. I just don't have any idea anymore what I am doing wrong.
It`s my first time asking a question myself here, so I hope the way I am doing is fine for making progress with the solution. Let me know if I can do something better/different or if I need to provide more information about something.
Thanks in advance!
Additional Information
For the sake of completeness:
The ParentViewController itself is embedded in a ContainerView,
which is owned by the RootViewController. I can't imagine this does
matter in any way, but this way we are not missing something out.
I am actually not showing the navigation action, because I want to
keep it as simple as possible. Furthermore the action is not problem,
it does what I want it to do. Correct me if I am wrong with this.
Possible solutions I found while researching, which did not work for me:
Setting window.delegate of the ChildViewControllers to NSApp.windows.first?.delegate
Setting the ChildViewController to becomeFirstResponder in its func viewWillAppear()
visualEffectView.addSubview(self.children[0].view, positioned: NSWindow.OrderingMode.above, relativeTo: nil)
Related problems/topics I found while researching:
Basic segmented control not working
Adding and Removing Child View Controllers
NSSegmentedControl - Odd appearance when placed in blur view
How to set first responder to NSTextView in Swift?
How to use #selector in Swift 2.2 for the first responder
Accessing methods, actions and/or outlets from other controllers with swift
How to use Child View Controllers in Swift 4.0 programmatically
Container View Controllers
issues with container view
Control a NSTabViewController from parent View
How to detect when NSTextField has the focus or is it`s content selected cocoa
SOLUTION
let parentViewControllerInstance = self.parent as! ParentViewController
segmentedControl.target = parentViewControllerInstance
In my case I just had to set the delegate as the target of the sendAction method.
Background
Ok, after hours of reading the AppKit Documentation I am now able to answer my own question.
First, debugging the UI showed that the problem was definitely not in the ViewHierarchy.
So I tried to think about the nature of NSButton and NSSegmentedControl. At some point I noticed that both are subclasses of NSControl.
class NSSegmentedControl : NSControl
class NSButton : NSControl
The AppKit Documentation says:
Discussion
Buttons are a standard control used to initiate actions within your app. You can configure buttons with many different visual styles, but the behavior is the same. When clicked, a button calls the action method of its associated target object. (...) You use the action method to perform your app-specific tasks.
The bold text points to the key of the solution – of its associated target object. Typically I define the action of an control item like this:
button.action = #selector(someFunc(_:))
This causes the NSControl instance to call this:
func sendAction(_ action: Selector?, to target: Any?) -> Bool
Parameter Description from the documentation:
Parameters
theAction
The selector to invoke on the target. If the selector is NULL, no message is sent.
theTarget
The target object to receive the message. If the object is nil, the application searches the responder chain for an object capable of handling the message. For more information on dispatching actions, see the class description for NSActionCell.
In conclusion the NSControl instance, which was firing the action method (in my case the NSSegmentedControl), had no target to send its action to. So it was only able to send its action method across the responder chain - which obviously has been nil while the first responder was located in another view.
I am using AVPlayerController and I have added a subview on top of AVPlayerController. As Soon the subview is displayed I want to shift focus from the player to the Subview and focus the UIButton added on top of that Subivew. I have tried preferredfocusview but its a read-only property and I cannot change it. Can anyone pelase assist. Thanks a lot.
First of all, preferredFocusView is deprecated so you should use preferredFocusEnvironments instead.
It is a computed property, which means you don't assign to it, you override it:
override var preferredFocusEnvironments: [UIFocusEnvironment] {
//if subview exists, return the subview or the button, whichever is focusable
return [subview]
//otherwise use the default implementation of AVPlayerController
return super.preferredFocusEnvironments
}
You should also call self.setNeedsFocusUpdate() and self.updateFocusIfNeeded() to request a focus update when you add the subview.
There is one controller and we are using a segmented controller on it. There is also a right navigation bar button and now we want to show a popupView on the segmented controller on click.
So I made a custom delegate for that bar button action.
When the button is clicked then the custom delegate method fires and it is working fine.
Now if I want to access the IBOutlet of popup view in that delegate action then it shows "Nil"
Main controller containing segmented controlle, container view and right navigation bar button:
// this is a protocol for button click
#objc protocol ClassList {
func classData(id:NSArray,className:NSArray)
}
// this is button action method of right navigation bar button
#IBAction func classFilter(sender: AnyObject) {
//dispatch_async(dispatch_get_main_queue()) {
self.delegate?.classData(self.classID as NSArray, className: self.dataPicker as NSArray)
//}
}
Segmented controller:
func classData(id: NSArray, className: NSArray) {
// method fire correctly
self.pickerView.hidden = false // crash app because pickerView reference nil
}
As soon as I've added custom bar navigation bar button item I've lost the ability to use the default function to go back. I'd like to have the ability to use "swipe from edge" to go back.
I've added the Edge Pan Gesture Recogniser and connected it to #IBAction, but the dismissing action happens completely as soon as the pan gesture is recognised.
Instead of slowly following my thumb (as seen in other apps), the current view moves out with predefined animation.
How to make the animation following my thumb using Edge Pan Gesture Recogniser?
#IBAction func edgeSwipe(sender: AnyObject) {
navigationController?.popViewControllerAnimated(true)
}
There's no need to add Edge Pan Gesture Recogniser. Following #beyowulf's suggestions I've been able to implement the swipe to go back feature that behaves the same way as the default system implementation does - the views edge follows my thumb as I swipe it to dismiss it.
So I've removed the ScreenEdge Pan Gesture Recogniser from the storyboard and also removed the related #IBAction.
I've made my first view controller to be the delegate for interactivePopGestureRecognizer. Here's the code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.interactivePopGestureRecognizer?.delegate = self
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
return navigationController?.viewControllers.count > 1 ? true : false
}
}