I want to show an NSPopover from an NSToolbarItem button in my toolbar.
(i.e. positioned below the button).
Ideally, I want to pass the NSView of the button to the popover to position it.
My question is, how do I get the NSView of the NSToolbarItem?
[toolbarbutton view] always returns nil.
The answer appears to be in the video for the 2011 WWDC Session 113, "Full Screen and Aqua Changes." Basically, put an NSButton inside the NSToolbaritem and use the view of that.
A blog post is here: http://www.yellowfield.co.uk/blog/?p=33, and a sample project is on github at http://github.com/tevendale/ToolbarPopover
All in the sprit of http://xkcd.com/979!
You can send the action directly from the NSButton enclosed in the NSToolbarItem (which is what you should generally do anyways, consider segmented controls, where each segment has its own target/action), and that will do the trick.
Instead of getting the view from the IBAction sender, connect an IBOutlet directly to the toolbar item and use that to get the relative view:
In your header file:
#property (weak) IBOutlet NSToolbarItem *theToolbarItem;
#property (weak) IBOutlet NSPopover *thePopover;
In your implementation file, to show the popover:
[self.thePopover showRelativeToRect:[[self.theToolbarItem view] bounds] ofView:[self.theToolbarItem view] preferredEdge:NSMinYEdge];
This will also work for showing popups from menu item selections inside a toolbar item.
While I did achieve that the Popover was shown using the approach mentioned by Stuart Tevendale, I did run into problems when I tried to validate (enable / disable) the NSToolbarItems using the NSToolbarDelegate:
-(BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
BOOL enable = YES;
NSString *identifier = [toolbarItem itemIdentifier];
// This does never get called because I am using a button inside a custom `NSToolbarItem`
if ([identifier isEqualToString:#"Popover"]) {
return [self someValidationMechanism];
}
// For this the validation works when I am using a standard `NSToolbarItem`
else if ([identifier isEqualToString:#"StandardToolbarItem"]){
return [self someOtherValidationMechanism];
}
return enable;
}
So I would advise not to display a Popover from NSToolbarItem. An alternative might be to show a Page Sheet: How to show a NSPanel as a sheet
Related
How to change image by click on that image in Iphone.Instead of button click I want to click on image.Which method can I use UIImageView.
Use a button and make your picture the picture for the button. You can do all this in Storyboard and then right click the button and drag to the view you want to go to. It should work just like that.
After you drag the button to the storyboard go to the property inspector. Make the button a custom button in the drop down list and then use the image field to select your image. You will need to size the button correctly and set the fill property to "size to fit." the other button behaviors are all customizable if you don't want normal button behavior, i.e. visually reacting to a touch.
This can be done by using touchevents also
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([[touches anyObject] view] == self.m_ImageViewObjectName) {
//Here you can change the image of UIImageView
}
}
You have to set tapRecognizer for your ImageView. It'll detect tap on your ImageView and call the method -clickEventOnImage.
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(clickEventOnImage:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
//Don't forget to set the userInteractionEnabled to YES, by default It's NO.
myImageView.userInteractionEnabled = YES;
[myImageView addGestureRecognizer:tapRecognizer];
Add your clickEventOnImage: will look like this :
-(void) clickEventOnImage:(id) sender
{
[myImageView setImage:[UIImage imageNamed: #"myNewImageName.png"]];
}
[self.scrollView scrollRectToVisible:textField.bounds animated:YES];
I can't seem to get my UIScrollView to scroll at all so that it doesn't obscure my UITextField. I thought that scrollRectToVisible would be my savior but it looks like a no go. Maybe I'm missing something like translating the coordinates of my textField to my scrollView. Either way check out my sample project.
https://github.com/stevemoser/Programming-iOS-Book-Examples/tree/master/ch20p573scrollViewAutoLayout2
Oh, and this project might be missing the delegate connection but I checked that and it still doesn't scroll.
I've seen other questions similar to this but none that mention Autolayout.
I was having issues with scrollRectToVisible:: as well after converting to Auto Layout. I just changed it to a direct call to setContentOffset:: and it started working again.
I had the same problem, I wanted to scroll an autolayouted UITextEdit into view without making it the first responder.
For me the issue was that the bounds of the UITextField were set later on during the auto layout pass, so if you do it immediately after setting up the layout the bounds are not valid yet.
To workaround I did create a descendant of UITextField, did overwrite setBounds: and added a 0 timer to scroll into view "later on" (You can't scroll in that moment because the auto layout pass of the system might no be finished at that point)
#interface MyTextField: UITextField
{
bool _scrollIntoView;
}
..
#end
#implementation MyTextField
-(void)setBounds:(CGRect)bounds
{
bool empty=CGRectIsEmpty(self.bounds);
bool isFirstResponder=self.isFirstResponder;
[super setBounds:bounds];
if (empty && !isFirstResponder && _scrollIntoView)
[self performSelector:#selector(scrollIntoViewLater) withObject:nil afterDelay:0];
else if (empty && isFirstResponder)
[self performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0];
}
-(void)scrollIntoViewLater
{
CGRect r=[scrollView convertRect:self.bounds fromView:self];
[scrollView scrollRectToVisible:r animated:TRUE];
}
#end
If the field should be additionally editable with the on screen keyboard, simply call becomeFirstResponder later on: it scrolls automagically into view above the keyboard using the private scrollTextFieldToVisible API which in turn calls scrollRectToVisible:animated: of the scrollview.
Your sample link is broken btw...
I have a view-based NSTableView. Each view in the table has a custom text field.
I'd like to fire an action when the user clicks on the text field (label) inside the table's view (imagine having a hyperlink with a custom action in each table cell).
I've created a basic NSTextField subclass to catch mouse events. However, they only fire on the second click, not the first click.
I tried using an NSButton and that fires right away.
Here's the code for the custom label:
#implementation HyperlinkTextField
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"link mouse down");
}
- (void)mouseUp:(NSEvent *)theEvent {
NSLog(#"link mouse up");
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
return YES;
}
#end
Had the same problem. The accepted answer here didn't work for me. After much struggle, it magically worked when I selected "None" as against the default "Regular" with the other option being "Source List" for the "Highlight" option of the table view in IB!
Edit: The accepted answer turns out to be misleading as the method is to be overloaded for the table view and not for the text field as the answer suggests. It is given more clearly at https://stackoverflow.com/a/13579469/804616 but in any case, being more specific feels a bit hacky.
It turned out that NSTableView and NSOultineView handle the first responder status for NSTextField instances differently than for an NSButton.
The key to get the label to respond to the first click like a button is to overwrite [NSResponder validateProposedFirstResponder:forEvent:] to return YES in case of my custom text field class.
Documentation:
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html#//apple_ref/occ/instm/NSResponder/validateProposedFirstResponder:forEvent:
The behavior that you're seeing is because the table view is the first responder, which it should be or the row won't change when you click on the label -- this is the behavior that a user expects when clicking on a table row. Instead of subclassing the label, I think it would be better to subclass the table view and override mouseDown: there. After calling the super's implementation of mouseDown:, you can do a hit test to check that the user clicked over the label.
#implementation CustomTable
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown:theEvent];
NSPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSView *theView = [self hitTest:point];
if ([theView isKindOfClass:[NSTextField class]])
{
NSLog(#"%#",[(NSTextField *)theView stringValue]);
}
}
#end
In the exact same situation, embedding an NSButton with transparent set to true/YES worked for me.
class LinkButton: NSTextField {
var clickableButton:NSButton?
override func viewDidMoveToSuperview() {
let button = NSButton()
self.addSubview(button)
//setting constraints to cover the whole textfield area (I'm making use of SnapKit here, you should add the constraints your way or use frames
button.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(NSEdgeInsetsZero)
}
button.target = self
button.action = Selector("pressed:")
button.transparent = true
}
func pressed(sender:AnyObject) {
print("pressed")
}
You use window.makeFirstResponser(myTextfield) to begin editing the text field. You send this message from the override mouseDown(withEvent TheEvent:NSEvent) method
I have a PopoverController view that allows a user to download a file. On button press, the popOver view will expand in size, display download status, and the main view controller will be obscured by an unhidden "cover" view that has been added to the PopoverController's "passThroughViews" property so that the user can not accidentally dismiss the pop over while the file is downloading.
My problem is that, in storyboards, my main viewController is embedded in a Navigation Controller. I can't seem to cover the navigation controller's bar with a view in the storyboard, and if the user presses anywhere on the navigation bar then the popover will disappear and the user will lose the download's progress bar.
How do I either cover up the navigation bar with my "cover" view, or how do I add the navigation bar's view to my popOverController's passThroughViews?
Opening the Popover from the main viewController:
- (IBAction)openDataOptionsPopOver:(id)sender
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
PopOverViewController *optionsWindow = [storyboard instantiateViewControllerWithIdentifier:#"dataOptions"];
self.popUp = [[UIPopoverController alloc] initWithContentViewController:optionsWindow];
[self.popUp setDelegate:self];
[nextNavButton setEnabled:NO]; //Disabling barButtonItem on the navigationController
optionsWindow.containerPopOver = self.popUp; //Pointer to the popover, to resize it later.
optionsWindow.coverView = self.coverView; //Pointer to the coverView, to (un)hide later
[popUp presentPopoverFromRect:[sender frame] inView:[sender superview] permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}
Setting the passThroughViews property inside of the PopoverViewController:
//Expands the popOver on press of "refreshFileButton" to display progressView
-(void) explodeWindow
{
//setting self.navigationController.view and ...visibleViewController.view here didn't seem to work ...
[containerPopOver setPassthroughViews:[NSArray arrayWithObjects:coverView, nil]];
[containerPopOver setPopoverContentSize:CGSizeMake(600, 400) animated:YES];
[titleBarItem setTitle:#"Downloading File. Please Wait ..."];
[refreshFileButton setHidden:YES];
[progressView setHidden:NO];
[downloadLabel setHidden:NO];
[coverView setHidden:NO];
[progressView setProgress:0.0 animated:NO];
}
I've tried adding self.navigationController.view to passThroughViews with no success--it actually turns out to be a null pointer. And I can't seem to place a UIView at any level in storyboards that will cover all my controls without obscuring the popOver. What am I missing here? And thanks for reading.
Edit:
As Aglaia points out below out, implementing the following, and avoiding passThroughViews, is probably the best way to do this.
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
//Don't dismiss our popover when the view covering our controls is present
if([coverView isHidden]){
return YES;
}else{
return NO;
}
}
Maybe there is something I am missing, but why don′t you just implement a new view controller with its navigation bar set to none and present it modally on button press? Then when the download is finished you just dismiss the view controller.
If you want the user to see the underlying view you can use a UIAlertView instead.
Alternatively set you view controller as the delegate of the popover controller and forbid the user to dismiss your popover on touch outside through
- (BOOL) popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return NO;
}
Then when you want to dismiss it call dismissPopoverAnimated:
to cover the whole screen including navigation bar:
[myView setFrame:[[UIScreen mainScreen] bounds];
[self.navigationController.view addSubview:myView];
Alright, so I made a popover from my main view and all that good stuff. But I want to have my popover call an action in my main view when a button within the popover is pressed.
MainView *mainView = [[MainView alloc] initWithNibName:#"MainView" bundle:nil];
[mainView doStuff];
The "dostuff" function changes some elements within the view. For example, the color of the toolbar is supposed to be changed. I've put a print command and the print command executes. But for some reason, the toolbar won't change color.
I've imported the header of MainView into the popover.
I did an #class thingy for MainView in my popover.
doStuff is declared in MainView's header.
The IBOutlets are declared too, and connected.
Any ideas?
Well its disappointing that we have no direct method that can be used to check in which view (view controller) the popover is shown. The thing that I am doing in tabbased application is:
New_iPadAppDelegate *appDel = (New_iPadAppDelegate *)[[UIApplication sharedApplication] delegate];
NSArray *viewConts = [(UINavigationController *)[[appDel tabBarController] selectedViewController] viewControllers];
MainViewController *viewController = (MainViewController *)[viewConts lastObject];
if([[viewController popoverController] isPopoverVisible]){
[viewController doStuff];
}
Hope this helps,
I know this is not the best way, hoping apple thinks about this issue, or if somebody has devised a work around.
Thanks,
Madhup