XCode IBAction NSButton to show/hide separate image? - macos

I have an IBAction that acts as a toggle switch for one of my functions; how can I make that same button show/hide a separate image depending on the isOn/isOff state?
Here's my code for the toggle function:
- (IBAction)toggleFunction:(id)sender;
{
if( isOn )
{
[self stopFunction: (NSButton *)sender];
isOn = NO;
}
else
{
[self startFunction: (NSButton *)sender];
isOff = YES;
}
}

You should use isOn instead isOff I think in Your else.
Try this:
- (IBAction)toggleFunction:(id)sender;
{
if( isOn == YES )
{
[self stopFunction: sender];
isOn = NO;
}
else
{
[self startFunction: sender];
isOn = YES;
}
}
** If this not working, say what's wrong. And also try NSLog Your functions to ensure that its called.

Related

Adding Quicklook to an existing NSTableView

I've been searching high and low for an example of an easy implementation of Quicklook to an existing NSTableView and while I've found example projects they're way beyond my skill set to disassemble and duct-tape into my project.
I can get the Quicklook window to appear when a button is pressed using
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
But I haven't the slightest clue on how to set the data source so that the window is populated with the file.
Simply put, is there any stupid-simple tutorial on how to do this...?
Create a class that conforms to the QLPreviewItem protocol and implement:
- (NSURL *)previewItemURL {
// <Return File URL for file you want to preview>
}
Have your class that triggers the preview panel implement QLPreviewPanelDataSource, QLPreviewPanelDelegate and add the following to your implementation:
# pragma mark - QuartzPanel
- (IBAction)togglePreviewPanel:(id)previewPanel {
if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
[[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
} else {
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
}
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel {
return YES;
}
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel {
_previewPanel = panel; // create a property to hold a reference to your panel
panel.delegate = self;
panel.dataSource = self;
}
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
_previewPanel = nil;
}
#pragma mark - QLPreviewPanelDataSource
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
return self.previewItems.count; // Items to preview of your custom subclass you created above
}
- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
return (id<QLPreviewItem>)self.previewItems[index];
}
#pragma mark - QLPreviewPanelDelegate
- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event {
// redirect all key down events to the table view
if ([event type] == NSKeyDown) {
NSString *key = [event charactersIgnoringModifiers];
if ([key isEqual:#" "]) {
[self togglePreviewPanel:self];
}
return YES;
}
return NO;
}

iOS8 keyboard confusion, why root view's hitTest method be triggered?

When touches on the keyboard area, the root view's method be triggered:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
I am very confused,anyone can help me?
In your applicationWillEnterForeground in AppDelegate, put this code.
It works for me, specially with KLCPopup
- (void)applicationWillEnterForeground:(UIApplication *)application{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
if (!IS_OS_8_OR_LATER) return;
[UIApplication.sharedApplication.windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *w, NSUInteger idx, BOOL *stop) {
if (!w.opaque && [NSStringFromClass(w.class) hasPrefix:#"UIText"]) {
// The keyboard sometimes disables interaction. This brings it back to normal.
BOOL wasHidden = w.hidden;
w.hidden = YES;
w.hidden = wasHidden;
*stop = YES;
}
}];}
This problem will appear in:
1、you changed keywindow‘s rootViewController;
2、enter background and return to the foreground;
So,restore UITextEffectsWindow can fixed it after your change everytime.
void TSRestoreKeyboardWindow(void)
{
if (!TSSystemVersionGreaterThanIOS8()) return;
[UIApplication.sharedApplication.windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *w, NSUInteger idx, BOOL *stop) {
if (!w.opaque && [NSStringFromClass(w.class) hasPrefix:#"UIText"]) {
// The keyboard sometimes disables interaction. This brings it back to normal.
BOOL wasHidden = w.hidden;
w.hidden = YES;
w.hidden = wasHidden;
*stop = YES;
}
}];
}

Changing Dynamic UIView subviews with single Tap in UIGestureRecognizer Method

UPDATE:Solved issue, see below!
The situation: I have several dynamically loaded UIViews on a UIScrollView in a nib.
Expected behavior: I want to single TAP any one of the UIViews and it will change background color to indicate it was tapped. If it was already tapped it should then change back to its initial look.
I have set up a UITapGesture recognizer on each of the UIViews and here is the selector method where I am doing the behavior. I have confused myself. I apologize for the sketchy logic here (it is a ruff draft). I have set up a isTapped BOOL set to "NO" initially in the init in the file.
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer {
isTapped = !isTapped;
UIView *v = gestureRecognizer.view;
NSInteger currentIndex = [studentCellArray indexOfObjectIdenticalTo:v];
if (oldIndex != currentIndex) {
isTapped = YES;
}
//check to see if obj in array then switch on/off
if ([tappedViewArray indexOfObjectIdenticalTo:v] != NSNotFound) {
oldIndex = currentIndex;
}
if (currentIndex == v.tag) {
isTapped = !isTapped;
}
if (isTapped) {
[tappedViewArray addObject:v];
[super formatViewTouchedNiceGrey:v];
}else{
[tappedViewArray removeObject:v];
[super formatViewBorder:v];
}
if (currentIndex == oldIndex) {
isTapped = !isTapped;
}
}
Actual Behavior: After Tapping the First UIView it selects fine and changes, a second tap will change it back, however after successive taps it stays selected. Also, if you select a UIView and go to another view - you have to double tap the successive views.
I would like to just tap once to turn off or on any of the UIViews in the scrollview.
UPDATE: Well, after some Hand writing and other vain attempts at trying to focus on this issue ---- I have solved it this way and it BEHAVES properly!
here is my solution:
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer {
isTapped = !isTapped;
UIView *v = gestureRecognizer.view;
NSInteger currentIndex = [studentCellArray indexOfObjectIdenticalTo:v];
if (((isTapped && currentIndex != oldIndex) || (!isTapped && currentIndex != oldIndex)) && [tappedViewArray indexOfObject:v] == NSNotFound) {
oldIndex = currentIndex;
[tappedViewArray addObject:v];
[super formatCornerRadiusWithGreyBackgrnd:v];
} else {
[super formatViewBorder:v];
[tappedViewArray removeObject:v];
}
}
So I hope this helps someone with this issue.
The key was to check for the isTapped and indexes being not equal AND the view object NOT being in the array I was assembling to indicate items touched/Tapped....

Respond to mouse events in text field in view-based table view

I have text fields inside a custom view inside an NSOutlineView. Editing one of these cells requires a single click, a pause, and another single click. The first single click selects the table view row, and the second single click draws the cursor in the field. Double-clicking the cell, which lets you edit in a cell-based table view, only selects the row.
The behavior I want: one click to change the selection and edit.
What do I need to override to obtain this behavior?
I've read some other posts:
The NSTextField flyweight pattern wouldn't seem to apply to view-based table views, where the cell views are all instantiated from nibs.
I tried subclassing NSTextField like this solution describes, but my overridden mouseDown method is not called. Overridden awakeFromNib and viewWillDraw (mentioned in this post) are called. Of course mouseDown is called if I put the text field somewhere outside a table view.
By comparison, a NSSegmentedControl in my cell view changes its value without first selecting the row.
Here's the working solution adapted from the accepted response:
In outline view subclass:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// Forward the click to the row's cell view
NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSInteger row = [self rowAtPoint:selfPoint];
if (row>=0) [(CellViewSubclass *)[self viewAtColumn:0 row:row makeIfNecessary:NO]
mouseDownForTextFields:theEvent];
}
In table cell view subclass:
// Respond to clicks within text fields only, because other clicks will be duplicates of events passed to mouseDown
- (void)mouseDownForTextFields:(NSEvent *)theEvent {
// If shift or command are being held, we're selecting rows, so ignore
if ((NSCommandKeyMask | NSShiftKeyMask) & [theEvent modifierFlags]) return;
NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
for (NSView *subview in [self subviews])
if ([subview isKindOfClass:[NSTextField class]])
if (NSPointInRect(selfPoint, [subview frame]))
[[self window] makeFirstResponder:subview];
}
Had the same problem. After much struggle, it magically worked when I selected None as against the default Regular (other option is Source List) for the Highlight option of the table view in IB!
Another option is the solution at https://stackoverflow.com/a/13579469/804616, which appears to be more specific but a little hacky compared to this.
I'll try to return the favor... Subclass NSOutlineView and override -mouseDown: like so:
- (void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// Only take effect for double clicks; remove to allow for single clicks
if (theEvent.clickCount < 2) {
return;
}
// Get the row on which the user clicked
NSPoint localPoint = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSInteger row = [self rowAtPoint:localPoint];
// If the user didn't click on a row, we're done
if (row < 0) {
return;
}
// Get the view clicked on
NSTableCellView *view = [self viewAtColumn:0 row:row makeIfNecessary:NO];
// If the field can be edited, pop the editor into edit mode
if (view.textField.isEditable) {
[[view window] makeFirstResponder:view.textField];
}
}
You really want to override validateProposedFirstResponder and allow a particular first responder to be made (or not) depending on your logic. The implementation in NSTableView is (sort of) like this (I'm re-writing it to be pseudo code):
- (BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {
// We want to not do anything for the following conditions:
// 1. We aren't view based (sometimes people have subviews in tables when they aren't view based)
// 2. The responder to valididate is ourselves (we send this up the chain, in case we are in another tableview)
// 3. We don't have a selection highlight style; in that case, we just let things go through, since the user can't appear to select anything anyways.
if (!isViewBased || responder == self || [self selectionHighlightStyle] == NSTableViewSelectionHighlightStyleNone) {
return [super validateProposedFirstResponder:responder forEvent:event];
}
if (![responder isKindOfClass:[NSControl class]]) {
// Let any non-control become first responder whenever it wants
result = YES;
// Exclude NSTableCellView.
if ([responder isKindOfClass:[NSTableCellView class]]) {
result = NO;
}
} else if ([responder isKindOfClass:[NSButton class]]) {
// Let all buttons go through; this would be caught later on in our hit testing, but we also do it here to make it cleaner and easier to read what we want. We want buttons to track at anytime without any restrictions. They are always valid to become the first responder. Text editing isn't.
result = YES;
} else if (event == nil) {
// If we don't have any event, then we will consider it valid only if it is already the first responder
NSResponder *currentResponder = self.window.firstResponder;
if (currentResponder != nil && [currentResponder isKindOfClass:[NSView class]] && [(NSView *)currentResponder isDescendantOf:(NSView *)responder]) {
result = YES;
}
} else {
if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown) {
// If it was a double click, and we have a double action, then send that to the table
if ([self doubleAction] != NULL && [event clickCount] > 1) {
[cancel the first responder delay];
}
...
The code here checks to see if the text field
cell had text hit. If it did, it attempts to edit it on a delay.
Editing is simply making that NSTextField the first responder.
...
}
I wrote the following to support the case for when you have a more complex NSTableViewCell with multiple text fields or where the text field doesn't occupy the whole cell. There a trick in here for flipping y values because when you switch between the NSOutlineView or NSTableView and it's NSTableCellViews the coordinate system gets flipped.
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown: theEvent];
NSPoint thePoint = [self.window.contentView convertPoint: theEvent.locationInWindow
toView: self];
NSInteger row = [self rowAtPoint: thePoint];
if (row != -1) {
NSView *view = [self viewAtColumn: 0
row: row
makeIfNecessary: NO];
thePoint = [view convertPoint: thePoint
fromView: self];
if ([view isFlipped] != [self isFlipped])
thePoint.y = RectGetHeight(view.bounds) - thePoint.y;
view = [view hitTest: thePoint];
if ([view isKindOfClass: [NSTextField class]]) {
NSTextField *textField = (NSTextField *)view;
if (textField.isEnabled && textField.window.firstResponder != textField)
dispatch_async(dispatch_get_main_queue(), ^{
[textField selectText: nil];
});
}
}
}
Just want to point out that if all that you want is editing only (i.e. in a table without selection), overriding -hitTest: seems to be simpler and a more Cocoa-like:
- (NSView *)hitTest:(NSPoint)aPoint
{
NSInteger column = [self columnAtPoint: aPoint];
NSInteger row = [self rowAtPoint: aPoint];
// Give cell view a chance to override table hit testing
if (row != -1 && column != -1) {
NSView *cell = [self viewAtColumn:column row:row makeIfNecessary:NO];
// Use cell frame, since convertPoint: doesn't always seem to work.
NSRect frame = [self frameOfCellAtColumn:column row:row];
NSView *hit = [cell hitTest: NSMakePoint(aPoint.x + frame.origin.x, aPoint.y + frame.origin.y)];
if (hit)
return hit;
}
// Default implementation
return [super hitTest: aPoint];
}
Here is a swift 4.2 version of #Dov answer:
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if (event.clickCount < 2) {
return;
}
// Get the row on which the user clicked
let localPoint = self.convert(event.locationInWindow, from: nil)
let row = self.row(at: localPoint)
// If the user didn't click on a row, we're done
if (row < 0) {
return
}
DispatchQueue.main.async {[weak self] in
guard let self = self else {return}
// Get the view clicked on
if let clickedCell = self.view(atColumn: 0, row: row, makeIfNecessary: false) as? YourOutlineViewCellClass{
let pointInCell = clickedCell.convert(localPoint, from: self)
if (clickedCell.txtField.isEditable && clickedCell.txtField.hitTest(pointInCell) != nil){
clickedCell.window?.makeFirstResponder(clickedCell.txtField)
}
}
}
}

How can I handle ESC key in Cocoa app?

I made an app switching to full screen mode. I want to use ESC key to escaping fullscreen mode, but binding menu item to ESC key in IB is removed at runtime. How can I keep ESC key binding to a menu item?
Preferred way to handle escape key in Cocoa is this as like #Josh Caswell said.
#pragma mark - NSResponder
- (void)cancelOperation:(id)sender
{
[self exitFullScreen];
}
One way to capture keyboard events involves subclassing:
Subclass your full-screen class (e.g.) NSView.
Add the method - (void) keyDown:(NSEvent *)theEvent to the subclass implementation.
Open up InterfaceBuilder and select the full-screen class that you previously created.
Change its class to your new subclass.
The subclass looks something like:
MySubclass.h
#interface MySubclass : NSView {
}
#end
MySubclass.m
#import <Carbon/Carbon.h>
#implementation MySubclass
- (void)keyDown:(NSEvent *)theEvent
{
switch([theEvent keyCode]) {
case kVK_Escape:
NSLog(#"ESC");
// Call the full-screen mode method
break;
default:
[super keyDown:theEvent];
}
}
#end
This doesn't bind the ESC key to the menu item, but it does give you equivalent functionality (and a bit more flexability since you can intercept all keyboard events).
Many people try to implement esc key functionality. There is cancelOperation in the responder chain to handle escape events.
//WRONG
- (void)keyDown:(NSEvent *)event
{
//unichar character = 0;
//if ([event type] == NSEventTypeKeyDown) {
// if ([[event charactersIgnoringModifiers] length] == 1) {
// character = [[event characters] characterAtIndex:0];
// }
//}
switch (character) {
//THIS IS WRONG correct is to implement interpretKeyEvents+moveRight
//case NSRightArrowFunctionKey:
// [self moveSelectedIndexRight];
// break;
//THIS IS WRONG correct is to implement interpretKeyEvents+ moveLeft
//case NSLeftArrowFunctionKey:
// [self moveSelectedIndexLeft];
// break;
//THIS IS WRONG correct is to implement interpretKeyEvents+ moveLeft
//case NSCarriageReturnCharacter:
// [self dismissWithCurrentlySelectedToken];
// break;
default:
[self interpretKeyEvents:#[event]];
[super keyDown:event]
break;
}
}
//CORRECT
- (void)keyDown:(NSEvent *)event
{
[self interpretKeyEvents:#[event]];
[super keyDown:event];
}
`/* Catch the commands interpreted by interpretKeyEvents:. Normally, if we don't implement (or any other view in the hierarchy implements) the selector, the system beeps. Menu navigation generally doesn't beep, so stop doCommandBySelector: from calling up t`he hierarchy just to stop the beep.
*/
- (void)doCommandBySelector:(SEL)selector {
if ( selector == #selector(moveRight:)
|| selector == #selector(moveLeft:)
|| selector == #selector(cancelOperation:)
|| selector == #selector(insertNewline:) )
{
[super doCommandBySelector:selector];
}
// do nothing, let the menu handle it (see call to super in -keyDown:)
// But don't call super to prevent the system beep
}
- (void)cancelOperation:(id)sender
{
//do your escape stuff
}
- (void)insertNewline:(id)sender
{
//do your enter stuff
}
- (void)moveRight:(nullable id)sender
{
[self moveSelectedIndexRight];
}
- (void)moveLeft:(nullable id)sender
{
[self moveSelectedIndexLeft];
}
I needed to dodge WKWebView crashes when ESC is pressed(?) so I sub-class it, and added:
import Carbon.HIToolbox
override func keyDown(with event: NSEvent) {
if event.keyCode == UInt16(kVK_Escape) {
// We crash otherwise, so just close window
self.window?.performClose(event)
}
else
{
// still here?
super.keyDown(with: event)
}
}

Resources