Manipulating an NSSavePanel's accessoryView? - objective-c

I have an NSSavePanel set up with an accessory view that contains a format popup menu and a tabless tabview with various options regarding the selected format. It shows up on the panel correctly, but I cannot seem to adjust its size.
Calling -setFrameSize on the accessoryView results in odd and incorrect resizing once, and then no response for any subsequent calls. Other controls in the view flat out refuse to respond to any calls at all.
Does NSSavePanel work like NSSearchField, which only uses its assigned menu as a template? If so, how can I manipulate the specific instance of the accessory view in the current active save panel?

My experience has been (especially with code that has to run on 10.5) that in order to handle permuting sizes in the accessoryView for NSSavePanel we had to remove (set it to nil) and re-add it. Under 10.7 (and, I believe, 10.6), it appears to be sufficient to call [savePanel layoutIfNecessary] after changing the frameSize.
In this case, since you mention that you are using invisible tab views. Usually a tab view has a consistent size. If you're looking to resize the NSSavePanel based on the contents of the various subviews, you may want to keep them as separate views (in the same or other NIB files) and load them as child views in the NSSavePanel.
I've successfully done this in a situation where the NIBs were dynamically loaded from an on-disk list of plug-in modules, where I used a single overall view that contained the popup menu and then I resized that view using -setFrameSize: in order to change it prior to adding the child view to it. Then I used addSubview: to add the subview to my existing view and called [savePanel layoutIfNeeded] after changing the size and adding the subview.
Here is the snippet of four adding the exportAccessoryViewController (this for us is what changes based on the selection of the popup menu) to our existing exportFormatOptionsView view (which contains the popup menu):
NSSize currentSize = [exportFormatOptionsView bounds].size;
NSView *newView = [exportAccessoryViewController view];
NSSize newSize = NSMakeSize( currentSize.width, currentSize.height+[newView bounds].size.height);
// resize the parent view
[exportFormatOptionsView setFrameSize: newSize];
// move the child view
[exportFormatOptionsView addSubview: newView];
of course, when you switch these out dynamically, you need to make sure you change the view size of the intermediate view back to the original size, so you can add the supplementary view in later, which I did like this:
NSSize currentSize = [exportFormatOptionsView bounds].size;
NSView *oldView = [exportAccessoryViewController view];
// pull it out
[oldView removeFromSuperview];
NSSize newSize = NSMakeSize( currentSize.width, currentSize.height-[oldView bounds].size.height);
[exportFormatOptionsView setFrameSize: newSize];
exportAccessoryViewController = nil;
Hope this helps.

Related

Preventing user to access a view in a NSView hierarchy

Maybe this is my strange understanding of the NSView hierarchy, but the question is:
I have a window with a SplitViewController which has the classic two child views.
When the user clic a button on the toolbar I add a 'work in progress' view as follows:
NSView* workingView;
// creating the view
...
[self.view addSubView:workingView];
where self is the SplitViewController object. My understanding (also from iOS programming) is that the split view controller has three views: left and right views and the workingView onto them(?)
The problem is that, even if workingView is covering the entire window, the two views are still accessible. In iOS developing we can set the userInteraction to NO so only the desired view is active. What about OSX?
Can I disable interaction with the split view controller views and let only the workingView be active?
Thanks
EDIT:
I followed #Wain hint and I created a new ViewController that contains my work in progress view.
I presented it using the presentViewController:animator technique. In this way I can hav more control about the positioning of the view.
In the custom animator I simply colored the view to see what is happening. Really the way the animator adds the new view controller seems nearly the same as adding a classic view onto the split view (like did before). In fact the result is the same: even if my working view covers two buttons belonging to the left side of the split view, they are clickable!
-(void)animatePresentationOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
NSViewController* bottomVC = fromViewController;
NSViewController* topVC = viewController;
topVC.view.wantsLayer = YES;
topVC.view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
topVC.view.alphaValue = 0.0f;
[bottomVC.view addSubview:topVC.view];
topVC.view.layer.backgroundColor = [[NSColor blueColor] CGColor];
topVC.view.frame = CGRectMake(10, 10, 100, 100);
[NSAnimationContext runAnimationGroup:^(NSAnimationContext* context){
context.duration = 1.0f;
topVC.view.animator.alphaValue = 1.0f;
} completionHandler:nil];
}
I know something is missing or wrong... May you help me in understanding what is going on here?
You should create a new view controller to manage your new view and then present it as a form sheet. The display style is slightly different on each platform but this is the correct approach.
An alternative is the screenshot the split view and pass the image to the new controller which is presented full size. It then applies an overlay and adds its own content view in front of the background image.

Adding NSView to window not on top?

I am adding a subview programmatically and adding it to the main windows context view to cover up the entire context view like so:
loadingView = [[LoadingView alloc] initWithFrame:[mainWindow.contentView frame]];
NSLog(#"%#", [mainWindow.contentView subviews]);
[mainWindow.contentView addSubview:loadingView];
NSLog(#"%#", [mainWindow.contentView subviews]);
[mainWindow makeFirstResponder:loadingView];
The NSLog's confirm that loadingView is being added last in the contentView subviews. I have also tried:
loadingView = [[LoadingView alloc] initWithFrame:[mainWindow.contentView frame]];
[mainWindow.contentView addSubview:loadingView positioned:NSWindowAbove relativeTo:nil];
[mainWindow makeFirstResponder:loadingView];
That didn't work either. For some reason the two tableviews (created in IB) at the bottom of the window are on top of the new view I've added. Here's a snapshot of the window, note that the red part is what should be on top with the progress bar and a few labels:
It's also worth noting that the view has it's alpha set to 0.9 which is why you can somewhat see behind it.
GW
If you place one view above another, the objects in the previous view will be visible in above view. What you need do is remove previous views from window and then add a new subview.
Try using:
//Create IBOutlet of your tableview in your .h file
IBOutlet NSTableView* yourTableView;
// Add this line where you are adding your subview to remove the tableview from superview.
[yourTableView removeFromSuperview];
// Then add your loading view as the subview
loadingView = [[LoadingView alloc] initWithFrame:[mainWindow.contentView frame]];
[mainWindow.contentView addSubview:loadingView];
Then whenever your want your tableView back use:
[window contentView]addSubview: yourTableView];
As long as you use nil here, you will not get predictable results.
[mainWindow.contentView addSubview:loadingView positioned:NSWindowAbove relativeTo:nil];
If you have not already done so, put all the other views inside a containing view.
Make the containing view the only view that is a direct child of the window content view.
Now add your subview with the above method and replace nil with a reference to the containing view.
In OS X, overlapping siblings have certain nuances when it comes to drawing. In your case, the loadingView and the two table views are siblings because they are all added as subviews of the window's content view and they overlap hence the nuances are coming into play.
From Apple's Documentation
For performance reasons, Cocoa does not enforce clipping among sibling
views or guarantee correct invalidation and drawing behavior when
sibling views overlap. If you want a view to be drawn in front of
another view, you should make the front view a subview (or descendant)
of the rear view.
I don't have the definitive solution for this but reading these should help improve your understanding for the long term.
http://www.cocoabuilder.com/archive/cocoa/327817-overlapping-sibling-views.html
Is there a proper way to handle overlapping NSView siblings?

UITableViewCells scroll horizontally using auto layout when changing to portrait mode

I am creating a screen that is primarily a UITableView with cells that have differing controls and layouts. I had the tableview working great until I switched to portrait mode where it allowed the tableview cells to scroll horizontally. The cells should never scroll horizontally; only vertically. When I navigate to the screen and I'm already in portrait mode it works fine, even going to landscape and back to portrait.
Some things to note are that I'm using and iPad, storyboards, auto layout, and the UITableViewCell is custom and does not have a nib (though it does the same thing when I tried using a nib). All controls and layout constraints are added programmatically. I am using a custom view controller with a view inside of it. Inside of the view I have the tableview and a toolbar. If I remove the view so that the tableview is directly under the custom view controller it works fine, but then I can't have the toolbar.
Here is the test code I am using to troubleshoot the problem:
// Remove all existing subviews
for(UIView *subView in self.contentView.subviews)
{
[subView removeFromSuperview];
}
UILabel *testLabel = [[UILabel alloc] init];
testLabel.text = #"Test";
[testLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
self.contentView addSubview:testLabel];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(testLabel);
NSString *format = [NSString stringWithFormat:#"V:|-[testLabel]-|"];
NSArray *constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:nil metrics:nil views:viewsDictionary];
//[self.contentView addConstraints:constraintsArray];
The scrolling works as intended until I uncomment the line to add the constraints. This code is in a method of my custom cell and gets called every time a cell is dequeued. I've tried adding the contraints individually, without using the visual format, with no luck. I have also done some research and found the code that removes the contraints from the cell and adds them to the contentView, but that hasn't helped either.
What do I need to do to keep the cells from scrolling horizontally when changing from landscape to portrait mode?
MORE INFO:
After some more debugging I found that the table view's content size is not shrinking horizontally. The width is staying at 1024.

How do you determine the size/frame of a custom UINavigationItem.titleView?

After creating a custom view and assigning it to the navigationItem.titleView property it is displayed like this
with the custom view filling the space between the two buttons. Therefore, the custom view is not centered on the navigation bar. How do I determine the frame of the view in the .titleView property? I want to center some text in the navigation bar, say under the time stamp.
If you really want to get titleView's frame (in your top-level view's coordinate space), you can do this:
[self.navBar layoutIfNeeded];
CGRect titleViewFrameInTopLevelViewSpace = [self.navigationItem.titleView
convertRect:self.navigationItem.titleView.bounds
toView:self.view];
You need to do layoutIfNeeded if you have just assigned titleView, because by default the navigation bar won't lay out its subviews until the next pass through the run loop.
That said, the titleView will be centered automatically, if it fits. I think you are setting the frame (or bounds) of your custom view too large. I tested this two ways:
I set up the titleView directly in the XIB. I simply dragged a View from the Object library onto the center of the navigation bar:
It sized the view to 128x33 automatically. The resize handles let me adjust the size. It stays centered until it overlaps the Categorize button. Then it shifts left.
I set the titleView property in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 33)];
customView.backgroundColor = [UIColor greenColor];
self.navItem.titleView = customView;
}
The result looks like this:
You could get the width of the leftBarButtonItem and the rightBarButtonItem after you've set them, and then use that to determine how to centre within the view you supply to titleView. That might do what you want?

Draw an NSView into an NSGraphicsContext?

I have a CGContext, which I can turn into an NSGraphicsContext.
I have an NSWindow with a clipRect for the context.
I want to put a scrollview into the context and then some other view into the scrollview so I can put an image into it... However, I can't figure out how to attach the scrollview into the context.
Eventually the view will probably be coming from a nib, but I don't see how that would matter.
I've seen this thread, (http://lists.apple.com/archives/quartz-dev/2006/Nov/msg00010.html) But they seem to leave off the step of how to attach the view into the context, unless there's something obvious I'm missing.
EDIT:
The reason I'm in this situation is that I'm writing a Mozilla Plugin. The browser gives me a CGContext (Quartz) and a WindowRef (QuickDraw). I can turn the CGContext into an NSGraphicsContext, and I can turn the windowRef into an NSWindow. From another data structure I also have the clipping rectangle...
I'm trying to draw an image into that context, with scrollbars as needed, and buttons and other UI elements... so I need (want) an NSView...
You can't put a view into a graphics context. A view goes either into another view, or as the content view of a window.
You can draw a view into a context by setting that context as the current context and telling the view to draw. You might do this as a means of rendering the view to an image, but otherwise, I can't think of a reason to do it. (Edit: OK, being a Netscape plug-in is probably a good reason.)
Normally, a view gets its own graphics context in NSView's implementation of the lockFocus method, which is called for you by display, which is called for you by displayIfNeeded (only if the view needs display, obviously), which is called for you as part of the event loop.
You don't need to create a context for a view except in very rare circumstances, such as the export-to-an-image case I mentioned. Normally, you let the view take care of that itself.
A partial solution?
What I have done currently is create a nib with a button in an IKImageView inside an NSScrollView. I load this in my plugin.
Then, since I have the NSWindow, I can get the contentView of the window. Then, I add the scrollview as subview of contentView.
It appears, but there seems to be some coordinate confusion about where the origin is. (top vs bottom) and since I'm mucking with the contentview of the WHOLE WINDOW, I'm doing some stuff very globally that perhaps I should be doing more locally. Like, the view never disappears, even when you close the tab, or go to another tab. (it does close when you close the window of course)
So, does this sound like a reasonable way of doing this? it feels a bit ... kludgy...
For future generations (and me when I forget how I did this and Google leads me back to my own question) Here's how I'm doing this:
I have a NIB with all my views, I load this on start-up.
on SetWindow, I set the clip rect and actually do the attaching:
NP_CGContext* npContext = (NP_CGContext*) window->window;
NSWindow* browserWindow = [[[NSWindow alloc] initWithWindowRef:npContext->window] autorelease];
NSView* cView = [browserWindow contentView];
NSView* hitView = [cView hitTest:NSMakePoint(window->x + 1, clip.origin.y + 1)];
if (hitView == nil || ![[hitView className] isEqualToString:#"ChildView"])
{
return;
}
superView = [hitView retain];
[superView addSubview: topView];
[superView setNextResponder: topView];
[topView setNextResponder: nil];
[browserWindow makeFirstResponder: topView];
To make sure I only addSubView once, I have a flag...
And then in handleEvent, I actually draw, Because I'm using an IKImageView, I can use the undocumented method: [imageView setImage: image]; which takes an NSImage.
So far this seems to be working for me. Hopefully this helps someone else.