Toolbar of NSScrollView inside SplitView [duplicate] - cocoa

It's easy to enable the "inspector bar" for text views so that a bar appears at the top of the screen with various formatting buttons. (Although I had some confusion until I learned to make sure I was selecting the text view in a scroll view, and not the scroll view itself). I can either programmatically use [textView setUsesInspectorBar:YES] or go to the Attributes Inspector and check the "Inspector Bar" box in the "Uses" section.
My question is, how can I further control the inspector bar? I'm having trouble finding information on it in the XCode documentation or online. I'd like to be able to position it in a different place on the screen. Being able to pick and choose which specific controls are in the bar would be great too.

The answer is, you aren't meant to further control the inspector bar. There's nothing in the documentation because, well, there's nothing. Apple's saying, use it or don't use it.
However, if you dig into it a bit, you will find that the inspector bar is a very interesting control. It's not displayed as part of the text view, but rather (privately) embedded in the "window view" itself. When I say "window view," I mean the superview of the content view.
If you list the subviews of that "window view":
NSLog(#"%#", [self.testTextView.window.contentView superview].subviews);
You end up with:
2012-08-02 15:59:30.145 Example[16702:303] (
"<_NSThemeCloseWidget: 0x100523dc0>", // the close button
"<_NSThemeWidget: 0x100525ce0>", // the minimize button?
"<_NSThemeWidget: 0x100524e90>", // the maximize button?
"<NSView: 0x100512ad0>", // the content view
"<__NSInspectorBarView: 0x100529d50>", // the inspector view
"(<NSToolbarView: 0x10054e650>: FD2E0533-AB18-4E7E-905A-AC816CB80A26)" // the toolbar
)
As you can see, AppKit puts the inspector bar at the same level as other top level window controls. Now this is getting into the land of private APIs, but simply tinkering with the "window view" shouldn't get any apps rejected.
You can try to get a reference to the __NSInspectorBarView from here. It seems like it is always the subview right after the content view, so something like this may work:
NSArray *topLevelViews = [self.testTextView.window.contentView superview].subviews;
NSUInteger indexOfContentView = [topLevelViews indexOfObject:self.testTextView.window.contentView];
if (indexOfContentView + 1 < topLevelViews.count) {
NSView *inspectorBar = [topLevelViews objectAtIndex:indexOfContentView + 1];
NSLog(#"%#", inspectorBar);
}
NSLog(#"%#", topLevelViews);
Since this immediately breaks if Apple changes the ordering of the top level views, it may not be a good idea for an application for production. Another idea is:
NSView *inspectorBarView = nil;
for (NSView *topLevelView in topLevelViews) {
if ([topLevelView isKindOfClass:NSClassFromString(#"__NSInspectorBarView")]) {
inspectorBarView = topLevelView;
}
}
NSLog(#"%#", inspectorBarView);
I don't know if the use of NSClassFromString() will pass App Store review guidelines, however, since once again, it's dependent on private APIs.
That being said, once you get a reference to the inspector bar view, things still don't work too well. You can try repositioning it at the bottom:
if (inspectorBarView) {
NSRect newFrame = inspectorBarView.frame;
newFrame.origin = NSZeroPoint;
[inspectorBarView setAutoresizingMask:NSViewMaxYMargin | NSViewMaxXMargin];
[inspectorBarView setFrame:newFrame];
}
But you end up with a misdrawn toolbar, so more work would be necessary there:
My ideas would be to try to shift the content view's height up to cover up the gray left-over area (which would have to be done every time the window is resized, maybe tinkering with autoresizing masks may make it easier) and custom draw a background for the inspector bar at the bottom.
EDIT
Oh, and you should file a feature request for this too. bugreport.apple.com

This is four years late, but I feel like someone on the internet may benefit from this in the future. I spent way too long trying to figure this out.
The inspector bar class, as the others have pointed out, seems to be a private class (__NSInspectorBarView). Therefore, it's probably not recommended to modify.
Nevertheless! The curious have to know. The inspector bar is inserted, at the time of this post (April 2016) into the window's accessory bar. You can get a list of accessory views as of OS X 10.10 using the array property in NSWindow called titlebarAccessoryViewControllers[].
Here's some Swift 2.0 code to do just that, assuming you haven't inserted any other accessory views into the window beforehand.
if window.titlebarAccessoryViewControllers.count > 0 {
let textViewInspectorBar = self.titlebarAccessoryViewControllers[0].view
let inspectorBarHeight: CGFloat = textViewInspectorBar!.frame.height // 26.0 pt
}
It's worth noting that accessory views are handled differently in full screen mode apps: https://developer.apple.com/library/mac/documentation/General/Conceptual/MOSXAppProgrammingGuide/FullScreenApp/FullScreenApp.html
I personally would not attempt to move an accessory view, as they are special kinds of views designed to stay in the toolbar (if I fully understood what I have read).
NSTitlebarAccessoryViewController Reference:
https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSTitlebarAccessoryViewController_Class/

Another 3 years on, but I suspect some will find this useful. My specific problem was in having a window fully filled by a tabView - ideal for setting various kinds of user defaults. Only one of these tab pages had a couple of text views for which I wanted the inspector bar visible. Tabbing to that page made the inspector bar appear, and pushed the whole lot down, ruining my carefully planned layouts. Tabbing away from the page did not hide it again.
The obvious thing was to get the inspector bar to appear on the relevant tab page only. Having got hold of it ("on the shoulders of giants" - thanks to giant Vervious) it is relatively easy to reposition it in the view hierarchy. You are still left with the problem of space for an empty toolbar pushing the content down. The window's view hierarchy changes radically when the inspector bar first appears, and I gave up on trying to do anything with it.
My solution is to increase the content view's height. (Why height and not origin I can't say.)
func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
if let inspectorBar = window!.titlebarAccessoryViewControllers.first(where:
{$0.view.className == "__NSInspectorBarView"} )?.view {
// move content view back to where it should be
var sz = window!.contentView!.frame.size
sz.height = window!.frame.size.height - 21
window!.contentView?.setFrameSize(sz)
// put the inspector where we want it
inspectorBar.removeFromSuperview()
let y = textPage.frame.size.height - inspectorBar.frame.size.height - 10
inspectorBar.setFrameOrigin(NSPoint(x: 0, y: y))
textPage.subviews.insert(inspectorBar, at: 0)
}
}
The code belongs in a NSTabViewDelegate which I made my window controller conform to, remembering to set the tabView's delegate to File's Owner in the xib, and is called whenever a new tab is selected. textPage is the view inside the relevant tabViewItem.
There are some arbitrary constants found by trial and error. The function only need run once. Repeated calls are harmless, but you could put in a flag to make an early return from subsequent calls.

You cannot do anything to position this thing.
Clearly, the corruption noted by #Vervious is real, but only if you do not have an NSToolBar.
You see, this inspectorBar is sadly a mostly private and mostly (publicly) undocumented but awesome tool. And it is very much intended for use in a window that has an NSToolBar visible... go figure.
After you have a toolbar added to your view
Still with a toolbar but hidden, and inspector bar is cool
(as in via the view menu or the method it invokes, which is toggleToolBarShown: and is an NSResponder friendly message )
So it is obvious, no you cannot do much with this. It's design is poorly documented. It works as intended as a pseudo accessory view bar under the place an NSToolbar goes (which is also not adjustable)

Related

NSTabView: Toolbar does not display

macOS 10.12.6; Xcode 9.3, storyboards
I have an NSTabView (tabless) that in itself contains two NSTabViews. One is tabless, the other one uses the 'toolbar' style.
When I start my app with the toolbar visible, everything is fine: it displays my tabs in the toolbar, I can change them, etc etc. Once I change to the other branch of my storyboard, the toolbar disappears... and when I come back, instead of a toolbar proper, with buttons and all that, I get a slightly widened bar that has no content in it.
I've set up a sample project to show my problem, where - for ease of switching - I have left the other two tabViewControllers to show their tabs (bottom/top, but this makes no difference).
1) First run (starting with 'toolbar' branch):
2) (not shown): switch to 'top' branch
3) After switching back to 'toolbar':
As a diagnostic aid, I've created a 'displayToolbarStatus' IBAction in the AppController:
#IBAction func displayToolbarStatus(_ sender: NSMenuItem){
if let window = NSApplication.shared.windows.first {
print(window.toolbar?.isVisible)
}
}
The results are as follows:
1) optional(true)
2) nil
3) optional(true)
which is very much in line with how things should work: the toolbar exists and is displayed, there is no toolbar, the toolbar exists and is displayed. Only, of course, it is not usable as a toolbar. (turning visibility off and on, or trying to force a size change with window.toolbar?.sizeMode = .regular has no effect whatsoever, nor does assigning icons to the toolbar items; the toolbar remains squashed and without functioning buttons.
I haven't worked in any depth with NSToolbar: is this a known problem with a workaround; is this new to Xcode 9.2 (which, after all, thinks that no window is valid, so obviously has some problems in that field)?
I really want to use the NSTabView 'toolbar' functionality: how do I proceed?
I've now had more time to play with toolbars. The 'weird' appearance of the non responsive toolbar is simply an empty toolbar, which gave me a clue as to what was going on.
0) The NSTabView overrides the window's toolbar; it does not hand back control when it vanishes; this means that if you have another toolbar in your window that will never show up the moment you're using an NSTabView with 'toolbar' style.
1) I have added a print statement to every relevant method in the ToolbarTabViewController and a 'Switching Tabs' in the containing TabViewController's DidSelect TabViewItem, as well as logging when Toolbar items are added to the window.
(The ToolbarTabViewController is the second controller in the containing TabViewController; it is selected. Otherwise the stack looks slightly different):
ViewDidLoad
Switching tabs
viewWillAppear
viewDidAppear
Switching tabs
Toolbar will add item
Toolbar will add item
viewWillAppear
viewDidAppear
Switching away to the other tab:
viewWillDisappear
Switching tabs
Toolbar did remove item
Toolbar did remove item
viewDidDisappear
So far, so good.
Switching back to the ToolbarTabController, we get
viewWillAppear
Switching tabs
viewDidAppear
Whatever method is called that adds the tabs-related items to the toolbar on first appearance does never get called again. (Note also that the the order of switching tabs and viewDidAppear is not consistent between the first and subsequent appearances.)
2) So, the logical thing to do seems to be to capture the items that are being created and to add them back for future iterations. In the ToolbarTabViewController:
var defaultToolbarItems: [NSToolbarItem] = []
#IBAction func addTabsBack(_ sender: Any){
if let window = NSApplication.shared.windows.first {
if let toolbar = window.toolbar{
for (index, item) in defaultToolbarItems.enumerated() {
toolbar.insertItem(withItemIdentifier: item.itemIdentifier, at: index)
}
}
}
}
override func toolbarWillAddItem(_ notification: Notification) {
// print("Toolbar will add item")
if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem {
if defaultToolbarItems.count < tabView.numberOfTabViewItems{
defaultToolbarItems.append(toolbarItem)
}
}
}
3) The last question was when (and where) to call addTabsBack() - I found that if I try to call it in viewWillAppear I start out with four toolbarItems, though the number of tabViewItems is 2. (and they do, in fact, seem to be duplications: same name, same functionality). Therefore, I am calling addTabsBack()in the surrounding TabViewController's 'didSelect TabViewItem' method - willSelect is too early; but didSelect gives me exactly the functionality I need.
4) There probably is a more elegant way of capturing the active toolbarItems, but for now, I have a working solution.

Leaving inputAccessoryView visible after keyboard is dismissed iOS8?

I want to make behavior like messaging app. I have been browsing Stack Overflow for solutions for this, and indeed there are plenty:
Leaving inputAccessoryView visible after keyboard is dismissed
This was the one that I found. But it seems things are a little different in iOS8. If I do the same thing in new iOS8 sdk, i get error:
'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<UICompatibilityInputViewController: 0x7fdcb3441b10> should have parent view controller:<ViewController: 0x7fdcb3b1e9f0> but requested parent is:<UIInputWindowController: 0x7fdcb684c000>'
In order to test this more I made a sample project, just one controller with view on the bottom:
Outlet is connected to bottom view, that only has UITextField on it. Am I missing something and how do i get the desired behvior?
iOS8 has a retain cycle with the inputAccessoryView. Here's a good post that seems to have a good workaround:
http://derpturkey.com/uitextfield-docked-like-ios-messenger/
You are adding the someView to multiple superViews, which leads to inconsistent hierarchies (which it is telling you).
When the keyboard gets activated, it calls the inputAccessoryView() method to see if it needs to stick anything on top of the keyboard, and adds that to its own superView. But you already added it to the view through your storyboard.
Now there are 2 ways you can solve this:
Make a .xib with your view and return that one in your inputAccessoryView(), not adding it to any superview yourself (the keyboard will.
Or make it completely in code using NSLayoutConstraint.
You can add the following code to your ViewController which will persist the view even when the keyboard is hidden.
override func canBecomeFirstResponder() -> Bool {
return true
}
Look at this GitHub repo for an example.

Why does ScrollView/Webview object in xcode include some kind of margin?

I'm really getting extremely mad with Xcode, trying to get a ScrollView correctly working. I'm having in fact 2 problems, but I'll separate it in two questions.
First of all, check out this screenshot.
Whether I drag the ScrollView to the top of view or just to the bottom border of the navigation bar, the web view (white square) stays about 60px from the nav bar, as you can see in the simulator.
The only way for me to have the web view aligned just below the nav bar is by dragging the Scrollview to the top of the view, at the top of the nav bar, and dragging the web view all the way to the top too.. Which places (in Storyboard) the web view behind the nav bar. Seems a bit odd, isn't it? The web view object has no attributes assigned to it which could have created that strange space.
What am I doing wrong?
Note: I have less than 40 hours of Xcode experience resulting in knowing nothing of Objective-C lines, but the Xcode interface itself. Learning by tutorials and practice
In the view controller add this method...
- (BOOL) automaticallyAdjustsScrollViewInsets
{
return NO;
}
This will fix it for you.

NSToolbar special area

I like to try to completely take over the area where the NSToolbar resides so I can put my own custom controls, views and background. The advantages of using this area are:
Any sliding panels appear below the toolbar area instead of just the title bar.
In Lion, the toolbar area comes down along with the menu bar when the mouse is at the top of the screen.
I have tried using a borderless window, and implementing my own custom views within it but unfortunately I lose the above advantages as well as having a few other minor problems.
My current method is to use the undocumented method '_toolbarView' with the NSToolbar and add my custom view into its subviews. This works fine as I can turn off toolbar customisation. Unfortunately, the size of the toolbar is initialised with the items within that toolbar. Does anyone know if I can change the size of toolbar without adding a fake ToolbarItem?
Maybe there's also a better way of doing this that I am currently unaware of.
Thanks for any suggestions and comments.
No need to use any undocumented APIs. Just create a toolbar item with a custom view:
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
…
[item setView:myCustomToolbarView];
…
}
You can control your custom toolbar’s size using the item’s minSize and maxSize properties (e. g. in your NSWindowDelegate’s -windowDidResize:).
Remember to also update the toolbar display mode so it doesn't show item labels:
[toolbar setDisplayMode: NSToolbarDisplayModeIconOnly];

How to change the height of an NSWindow titlebar?

I want to change the height of an NSWindow titlebar.
Here are some examples:
And…
I could use an NSToolbar, but the problem is that I can't place views very height (For example: I can't place the segmentedControl higher than in the picture because there is still the titlebar)
If I remove the titlebar I can't place a NSToolbar and the window isn't movable.
Have you any ideas?
This is much easier than one would think. I too went on a quest to do something similar for my app.
Real App Store app:
My App Store app look-alike:
No disrespect to INAppStoreWindow, it is a very good implementation and solid. The only draw back I saw from it though was that there was a lot of drawing code along with hardcoded settings for the TitleBar colors which Apple can adjust at anytime.
So here is how I did it:
A) Create a standard window with a Title Bar, Close, Minimize, Shadow, Resize, Full Screen - Primary Window all set.
Note: You do not need a textured window nor should you set a title
B) Next add a standard toolbar with these settings:
Icon Only
Visible at Launch - ON
Customizable - OFF
Separator - ON
Size - Regular
Remove all the Toolbar Items and add only these in the following order
NSSegmentControl (51 x 24) -- | Flexible Space | -- NSSearchField (150 x 25)
C) In your content View directly under the toolbar add a regular sized NSButton set like so:
Bordered - OFF
Transparent - OFF
Title -
Image -
Position - Text below the button
Font - System Small 11
Ok, pretty easy so far, right?!
In your Window Controller or app delegate....
setup IBOutlet(s) to your NSButton(s)
Note: Remember to hook up your IBOutlet in interface builder
Ok don't be scared we have to write a tiny bit of code now:
In awakeFromNib or windowDidLoad....
Get the content views' superview (aka NSThemeView)
Remove your button from its superView
Set the frame of your button
Add the button back to the theme view
So the code would look similar to this:
NSView *themeView = [self.contentView superview];
NSUInteger adj = 6;
[self.btnFeatured removeFromSuperview];
self.btnFeatured.frame = NSMakeRect( self.btnFeatured.frame.origin.x,
self.window.frame.size.height - self.btnFeatured.frame.size.height - adj,
self.btnFeatured.frame.size.width, self.btnFeatured.frame.size.height);
[themeView addSubview:self.btnFeatured];
That's it! You can use your outlet to enable/disable your button, setup a mask image when selected, enable/disable the toolbar or even hide everything and add a window title. All of this without worry if Apple changes their standard Window Titlebars.
P.S. No private frameworks were used in this posting whatsoever!
INAppStoreWindow is a NSWindow subclass, it tell you how to change the height of title bar.
https://github.com/indragiek/INAppStoreWindow
http://iloveco.de/adding-a-titlebar-accessory-view-to-a-window/
This example tells you how to add buttons in the title bar.
You'd have to subclass NSWindow and do a custom window frame drawing. It's not only about a titlebar. It's about whole window frame (so you can, actually, put close/minimize/zoom buttons at the bottom if you wish).
A good starter is at "Cocoa with love" website.
There are a few new solutions based on INAppStoreWindow and without warning and log message, for anyone who wants to change the height of NStitlebar, change the position of traffic light, add an item(e.g. a NSbutton) on NStitlebar and change its position, please check below.
WAYWindow:
https://github.com/weAreYeah/WAYWindow
NStitlebar_with_item:
https://github.com/ZHANGneuro/NStitlebar_with_item

Resources