How to Disable Mouse Wheel Event in AVPlayerView? - objective-c

While AVPlayerView Watching,
Sliding Mouse Wheel makes pause movie then fast forward or rewind.
this makes process error.
I set AVPlayerView Mode as NONE, btw.
let me know how to disable mouse wheel event on AVPlayerView.

Swift 3 (forwards scrolling to the view underneath it):
class NonScrollableAVPlayerView : AVPlayerView
{
override func scrollWheel(with event: NSEvent)
{
if self.superview != nil
{
self.nextResponder?.scrollWheel(with: event)
}
}
}

Override hitTest:
#interface MyAVPlayerView : AVPlayerView
#end
#implementation MyAVPlayerView
- (NSView *)hitTest:(NSPoint)aPoint
{
if (prohibitScrolling) {
return nil;
} else {
return [super hitTest:aPoint];
}
}
#end

Related

NSTextField and NSTouchbar

I am implementing touchbar functionalities.
I want a specific touchbar to be displayed when editing an NSTextField.
I have tried to both methods :
set a touchbar using touchbar property :
field.touchBar = myTouchBar
and subclassing NSTextField to override makeTouchBar() function :
class MyTextField: NSTextField
{
override func makeTouchBar(){return myTouchBar}
}
Both methods show an empty touchbar when editing the field. Changing the isAutomaticTextCompletionEnabled and allowsCharacterPickerTouchBarItem properties does not change it - just making the corresponding buttons appear.
Doing exactly the same thing with an NSTextView - or many other type of NSView, however, works perfectly well.
Do you know if it is possible to have a custom toolbar when editing an NSTextField?
Thanks to #Willeke's answer, I have been able to find the solution. It is quite tricky, however :
First, subclass NSTextField to keep another NSTouchBar :
class MyTextField: NSTextField
{
private var innerTouchBar: Any?
var editor: NSText?
#available(OSX 10.12.2, *)
func setTouchBar(_ touchBar: NSTouchBar?)
{
innerTouchBar = touchBar
}
#available(OSX 10.12.2, *)
func getTouchBar() -> NSTouchBar?
{
innerTouchBar as? NSTouchBar
}
}
Then, subclass NSTextView to use a provided NSTouchBar :
#available(OSX 10.12.2, *)
class MyTextView: NSTextView
{
private var innerTouchBar: NSTouchBar?
convenience init(touchBar: NSTouchBar?)
{
self.init()
innerTouchBar = touchBar
}
override func makeTouchBar() -> NSTouchBar?
{
innerTouchBar
}
}
When the NSWindowController gets asked for the NSTextView of the NSTextField, then create a custom NSTextView with the innerTouchBar of the NSTextField :
extension MyWindowController
{
func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any?
{
if #available(OSX 10.12.2, *)
{
if let field = client as? MyTextField
{
if field.editor == nil
{
field.editor = SFTextView(touchBar: field.getTouchBar())
field.editor?.isFieldEditor = true
}
return field.editor
}
}
return nil;
}
}
Of course, do not forget to use MyTextField instead of NSTextField in the XIB or in your code, and to call the setTouchBar(_:) function first thing after creation.
Explanation
Every NSTextField has an underlying NSTextView, which is in fact the firstResponder, and the object whose touchBar is displayed. We cannot access the underlying NSTextView directly from NSTextField. Instead, the NSTextField asks the NWindowController which NSTextView to use. So when this happens, in windowWillReturnFieldEditor(_:,to:) of NSWindowController, we have to return a custom NSTextViewwith the correct touchBar.
I think this can apply to other things than touchBar...

Simultaneous UIGestureRecognizers [duplicate]

I d'like to combine a UILongPressGestureRecognizer with a UIPanGestureRecognizer.
The UIPanGestureRecognizer should start with a long press. Is there a simple way to do this? or do I really have to write my own gesture recognizer?
I wan't something like on the home screen. You press on an icon and after some time the icons start wobbling. Afterwards without releasing my finger from the screen I can start dragging the icon under my finger around.
actually, you don't have to combine gesture recognizers - you can do this solely with UILongPressGestureRecognizer... You enter StateBegan once your touch(es) have stayed within 'allowableMovement' for 'minimumPressDuration'. You stay in your continuous longPressGesture as long as you don't lift any of your fingers - so you can start moving your fingers and track the movement through StateChanged.
Long-press gestures are continuous. The gesture begins (UIGestureRecognizerStateBegan) when the number of allowable fingers (numberOfTouchesRequired) have been pressed for the specified period (minimumPressDuration) and the touches do not move beyond the allowable range of movement (allowableMovement). The gesture recognizer transitions to the Change state whenever a finger moves, and it ends (UIGestureRecognizerStateEnded) when any of the fingers are lifted.
I had a bit of a hard time for this problem. The accepted answer wasn't enough. No matter what I put in that method the pan or longpress handlers would get invoked. A solution I found was as follows:
Ensure the gesture recognizers' delegates are assigned to the same class (in my case self) and ensure the delegate class is a UIGestureRecognizerDelegate.
Add the following delegate method to your class (as per the answer above):
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Add the following delegate method to your class:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && ! shouldAllowPan) {
return NO;
}
return YES;
}
Then add either a property or ivar which will track if the pan should be allowed to begin (see method above). In my case BOOL shouldAllowPan.
Set the BOOL to NO in your init or viewDidLoad. Inside your longPress handler set the BOOL to YES. I do it like this:
- (void) longPressHandler: (UILongPressGestureRecognizer *) gesture {
if(UIGestureRecognizerStateBegan == gesture.state) {
shouldAllowPan = NO;
}
if(UIGestureRecognizerStateChanged == gesture.state) {
shouldAllowPan = YES;
}
}
Inside the panHandler I do a check on the BOOL:
- (void)panHandler:(UIPanGestureRecognizer *)sender{
if(shouldAllowPan) {
// do your stuff
}
And finally reset the BOOL within the panHandler:
else if(sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed || sender.state == UIGestureRecognizerStateCancelled) {
shouldAllowPan = NO;
}
And then go grab a beer to congratulate yourself. ;)
I found a solution:
This UIGestureRecognizerDelegate method does exactly what I looked for:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
Andy B's approach in Swift,
Add the UIGestureRecognizerDelegate delegate to the class
class ViewController: UIViewController, UIGestureRecognizerDelegate
Add a member variable
var shouldAllowPan: Bool = false
Add the gestures and need to add the pan gesture delegate to the VC. This is needed to fire off the shouldRecognizeSimultaneouslyWithGestureRecognizer and gestureRecognizerShouldBegin functions
// long press
let longPressRec = UILongPressGestureRecognizer(target: self, action: "longPress:")
yourView.addGestureRecognizer(longPressRec)
// drag
let panRec = UIPanGestureRecognizer(target: self, action: "draggedView:")
panRec.delegate = self
yourView.addGestureRecognizer(panRec)
Allow simultaneous gestures
func gestureRecognizer(UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
// println("shouldRecognizeSimultaneouslyWithGestureRecognizer");
return true
}
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
// We only allow the (drag) gesture to continue if it is within a long press
if((gestureRecognizer is UIPanGestureRecognizer) && (shouldAllowPan == false)) {
return false;
}
return true;
}
Inside the long press handler:
func longPress(sender: UILongPressGestureRecognizer) {
if(sender.state == .Began) {
// handle the long press
}
else if(sender.state == .Changed){
shouldAllowPan = true
}
else if (sender.state == .Ended) {
shouldAllowPan = false
}
}
For combinate more gesture :
Create a local variable var shouldAllowSecondGesture : Bool = false
Create the two recognizer
let longPressRec = UILongPressGestureRecognizer(target: self, action: #selector(self.startDrag(sender:)))
cell.addGestureRecognizer(longPressRec)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(sender:)))
cell.isUserInteractionEnabled = true
cell.addGestureRecognizer(panGestureRecognizer)
Extension your VC and implement GestureRecognizerDelegate for implemented this method.
extension YourViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// We only allow the (drag) gesture to continue if it is within a long press
if((gestureRecognizer is UIPanGestureRecognizer) && (shouldAllowPan == false)) {
return false
}
return true
}
#objc func startDrag(sender:UIPanGestureRecognizer) {
if(sender.state == .began) {
// handle the long press
}
else if(sender.state == .changed){
shouldAllowPan = true
}
else if (sender.state == .ended) {
shouldAllowPan = false
}
}
Read the "Subclassing Notes" section of Apple's UIGestureRecognizer Class Reference at:
https://developer.apple.com/library/prerelease/tvos/documentation/UIKit/Reference/UIGestureRecognizer_Class/
I solved this issue by implementing the desired functionality of the "action: Selector?" func of the UIPanGestureRecognizer within the "action: Selector?" func for the UILongPressGestureRecognizer.
As 'UILongPressGestureRecognizer' has no member 'translation', I calculated the translation by saving the position of the original touch and them extracting it from the actual touch position.
// in target class
var initialTouchX : CGFloat
var initialTouchX : CGFloat
// in the #objc func for the UILongPressGestureRecognizer
if sender.state == .began {
initialTouchX = sender.location(in: sender.view).x
initialTouchY = sender.location(in: sender.view).y
}
let translation = CGVector(dx: sender.location(in: sender.view).x - initialTouchX, dy: sender.location(in: sender.view).y - initialTouchY)

NSCollectionView doesn't popup context menu?

I've bounded the menu to the NSCollectionView in interface builder. But when I CTRL+click (right click) on it the menu is not showing.
I've tried adding some method to the NSCollectionView subclass. None of them is invoked:
+ (NSMenu*)defaultMenu
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
- (void)rightMouseDown:(NSEvent *)theEvent
- (void)sendEvent:(NSEvent *)theEvent
The only method which is invoked is:
- (NSView *)hitTest:(NSPoint)aPoint
Which means that the NSCollectionView receives the mouse events.
I've also tried to add the same methods to the subclass of NSCollectionViewItem, and the result is the same. Only hitTest: is called.
I had the same issue with the "new" NSCollectionView. The contextual menu is set up in the xib, and it is actually correctly triggered by an actual right-click on the mouse, and also by double-finger tab on the trackpad (if the user has set that option in the System Preferences), but not by control-click. Thus it seems like a legitimate bug or limitation with NSCollectionView, maybe dependent on how it is set up.
In any case, here is a shorter solution, this one in Swift, that assumes you have otherwise set up the contextual menu using the menu outlet for the collection view (or you have it set up as outlined in Apple's documentation).
You will need to create a subclass of NSCollectionView and choose the subclass for the collection view in the xib. Here is the code for the subclass:
import Cocoa
class MyCollectionView: NSCollectionView {
/// Fixes the behavior of collection view with control-click, that does not properly trigger the contextual menu.
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if event.type == .rightMouseDown || event.modifierFlags.contains(.control) {
rightMouseDown(with: event)
}
}
}
This works for me:
#interface MyCollectionView : NSView
-(void)mouseDown:(NSEvent *)theEvent;
#end
#implementation MyCollectionView
-(void)mouseDown:(NSEvent *)theEvent
{
NSMenu *theMenu = [[NSMenu alloc] initWithTitle:#"Contextual Menu"];
[theMenu insertItemWithTitle:#"Beep" action:#selector(beep) keyEquivalent:#"" atIndex:0];
[theMenu insertItemWithTitle:#"Honk" action:#selector(honk) keyEquivalent:#"" atIndex:1];
[NSMenu popUpContextMenu:theMenu withEvent:theEvent forView:self];
[super mouseDown:theEvent];
}
-(void)beep{
}
-(void)honk{
}
#end
I hope this helps.
Subclass the NSCollectionView
class OSCollectionView: NSCollectionView {
override func menu(for event: NSEvent) -> NSMenu? {
print("menu() called")
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Create a clone", action: #selector(clone(_:)), keyEquivalent: ""))
return menu
}
#objc
func clone(_ sender: Any){
//editDelegate?.terminateAndReplace(self)
print("Clone item")
}
}

Customize NSToolbar - Disable "Use small size"

How do I disable the "Use small size" option in the toolbar? I am using Xcode 4.
(That's the option that appears when users go to customize the Toolbar.)
If you're not distributing on the Mac App Store, and don't mind subclassing private methods, you can create an NSToolbarSubclass and override _allowsSizeMode: to return NO:
- (BOOL)_allowsSizeMode:(NSToolbarSizeMode)mode {
return mode != NSToolbarSizeModeSmall;
}
This has the added benefit of removing the checkbox from the customization sheet, as well.
You could subclass NSToolbar, override -setSizeMode: and in your implementation call [super setSizeMode: NSToolbarSizeModeRegular];.
If you're instantiating the toolbar in Interface Builder then make sure you assign your subclass to the toolbar in the nib.
#implementation RKToolbar
- (void)setSizeMode:(NSToolbarSizeMode)aSizeMode
{
[super setSizeMode:NSToolbarSizeModeRegular];
}
#end
This won't remove the checkbox from the customize panel but it will prevent it from doing anything.
There's not really a supported way to remove the checkbox. This does work but it's pretty hacky:
//in your NSToolbar subclass
- (void)runCustomizationPalette:(id)sender
{
[super runCustomizationPalette:sender];
NSWindow* toolbarWindow = [NSApp mainWindow];
NSWindow* sheet = [toolbarWindow attachedSheet];
for(NSView* view in [[sheet contentView] subviews])
{
if([view isKindOfClass:[NSButton class]])
{
if([[[(NSButton*)view cell] valueForKey:#"buttonType"] integerValue] == NSSwitchButton)
{
[view setHidden:YES];
}
}
}
}
Thanks to Rob Keniger for the excellent start. If you can have your custom toolbar as a delegate of your window, you can avoid having "Use small size" visible by getting at the sheet before it is displayed on screen. Do this by implementing [NSToolbar window:willPositionSheet:usingRect:] in the custom toolbar class. Elsewhere in your code, you'll need to do:
[myWindowWithToolbar setDelegate:myInstanceOfXXToolbar];
Here's the updated custom toolbar class:
#implementation XXToolbar
- (void)setSizeMode:(NSToolbarSizeMode)aSizeMode
{
[super setSizeMode:NSToolbarSizeModeRegular];
}
- (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect {
NSView *buttonView = nil;
for(NSView* view in [[sheet contentView] subviews])
{
if([view isKindOfClass:[NSButton class]])
{
if([[[(NSButton*)view cell] valueForKey:#"buttonType"] integerValue] == NSSwitchButton)
{
buttonView = view;
break;
}
}
}
if (buttonView) {
[buttonView setHidden:YES];
// This is important as it causes the sheet to redraw without the button off screen
[[sheet contentView] display];
}
return rect;
}
#end
Hope you find this useful.
Here's a Swift 2.2 version of #MacGreg's solution. You can keep your NSWindowDelegate wherever you like, just ensure at least the following is called:
var toolbar: UniformToolbar!
func window(window: NSWindow, willPositionSheet sheet: NSWindow, usingRect rect: NSRect) -> NSRect {
toolbar.removeSizeToggle(window: sheet)
return rect
}
Toolbar Subclass without the Checkbox
class UniformToolbar: NSToolbar {
override var sizeMode: NSToolbarSizeMode {
get {
return NSToolbarSizeMode.Regular
}
set { /* no op */ }
}
func removeSizeToggle(window window: NSWindow) {
guard let views = window.contentView?.subviews else { return }
let toggle: NSButton? = views.lazy
.flatMap({ (view: NSView) -> NSButton? in view as? NSButton })
.filter({ (button: NSButton) -> Bool in
guard let buttonTypeValue = button.cell?.valueForKey("buttonType")?.unsignedIntegerValue,
buttonType = NSButtonType(rawValue: buttonTypeValue)
else { return false }
return buttonType == .SwitchButton
})
.first
toggle?.hidden = true
window.contentView?.display()
}
}

Make a Cocoa application quit when the main window is closed?

How to make a Cocoa application quit when the main window is closed? Without that you have to click on the app icon and click quit in the menu.
You can implement applicationShouldTerminateAfterLastWindowClosed: to return YES in your app's delegate. But I would think twice before doing this, as it's really unusual on the Mac outside of small "utility" applications like Calculator and most Mac users will not appreciate your app behaving so strangely.
Add this code snippet to your app's delegate:
-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
return YES;
}
As the question is mainly about Cocoa programming and not about a specific
language (Objective-C), here is the Swift version of Chuck's and Steve's
answer:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
return true
}
// Your other application delegate methods ...
}
For Swift 3 change the method definition to
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
You should have an IBOutlet to your main window. For Example: IBOutlet NSWindow *mainWindow;
- (void)awakeFromWindow {
[mainWindow setDelegate: self];
}
- (void)windowWillClose:(NSNotification *)notification {
[NSApp terminate:self];
}
If this does not work you should add an observer to your NSNotificationCenter for the Notification NSWindowWillCloseNotification. Don't forget to check if the right window is closing.
This works for me.
extension MainWindowController: NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
if let window = notification.object as? NSWindow, let controller = window.windowController {
if window == self.window {
for window in self.childWindows {
print(" Closing \(window)")
window.close()
}
}
}
}
}