Create plain box window like this in Swift? - macos

Below is a screenshot of the 'Squish' app on the App Store. How am I able to make a window like that, with rounded corners and no separator between the titlebar and content?
The only difference is I want to have a title on the title bar.
So in short, how do make a window like in the image but with a title?

Create a new NSWindow in Xcode, then create an outlet for it, and set the titlebarAppearsTransparent property to true:
#IBOutlet weak var windowBlank: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
windowBlank.titlebarAppearsTransparent = true
windowBlank.backgroundColor = NSColor.whiteColor()
}
You also have to enable the Full size content view checkbox in the Attributes inspector.

Related

How to fix the postion of splitDividerView in NSSplitViewController

How to fix the postion of splitDividerView in NSSplitViewController
I had been search for years,but it doesn't work.Please help,Thanks!
Once this app start,the left view width is 150,but still i can resize both of the left and right view by dragging the splitDividerView. I have attatached a screenshot for reference
override func viewDidLoad() {
super.viewDidLoad()
self.splitView.wantsLayer = true
self.splitView.setPosition(150, ofDividerAt: 0)
}
screenshot for reference
I add two container view which is use for managing NSViewController , so get rid of NSSplitView and will save you hours

How to align a toolbar (or its items) with the leading edge of a split view controller's child?

In iOS, a toolbar can be added to any view. In macOS however, it seems only possible to add a toolbar to a window.
I'm working on an app with a split view controller with a toolbar but the toolbar's items only have a meaning with respect to the right view controller's context.
E.g. let's say I have a text editor of some sort, where the left pane shows all documents (like in the Notes app) and the right pane shows the actual text which can be edited. The formatting buttons only affect the text in the right pane. Thus, it seems very intuitive to place the toolbar within that right pane instead of stretching it over the full width of the window.
Is there some way to achieve this?
(Or is there a good UX reason why this would be a bad practice?)
I've noticed how Apple solved this problem in terms of UX in their Notes app: They still use a full-width toolbar but align the button items that are only related to the right pane with the leading edge of that pane.
So in case, there is no way to place a toolbar in a view controller, how can I align the toolbar items with the leading edge of the right view controller as seen in the screenshot above?
Edit:
According to TimTwoToes' answer and the posts linked by Willeke in the comments, it seems to be possible to use Auto Layout for constraining a toolbar item with the split view's child view. This solution would work if there was a fixed toolbar layout. However, Apple encourages (for a good reason) to let users customize your app's toolbar.
Thus, I cannot add constraints to a fixed item in the toolbar. Instead, a viable solution seems to be to use a leading flexible space and adjust its size accordingly.
Initial Notes
It turns out this is tricky because there are many things that need to be considered:
Auto Layout doesn't seem to work properly with toolbar items. (I've read a few posts mentioning that Apple has classified this as a bug.)
Normally, the user can customize your app's toolbar (add and remove items). We should not deprive the user of that option.
Thus, simply constraining a particular toolbar item with the split view or a layout guide is not an option (because the item might be at a different position than expected or not there at all).
After hours of "hacking", I've finally found a reliable way to achieve the desired behavior that doesn't use any internal / undocumented methods. Here's how it looks:
How To
Instead of a standard NSToolbarFlexibleSpaceItem create an NSToolbarItem with a custom view. This will serve as your flexible, resizing space. You can do that in code or in Interface Builder:
Create outlets/properties for your toolbar and your flexible space (inside the respective NSWindowController):
#IBOutlet weak var toolbar: NSToolbar!
#IBOutlet weak var tabSpace: NSToolbarItem!
Create a method inside the same window controller that adjusts the space width:
private func adjustTabSpaceWidth() {
for item in toolbar.items {
if item == tabSpace {
guard
let origin = item.view?.frame.origin,
let originInWindowCoordinates = item.view?.convert(origin, to: nil),
let leftPane = splitViewController?.splitViewItems.first?.viewController.view
else {
return
}
let leftPaneWidth = leftPane.frame.size.width
let tabWidth = max(leftPaneWidth - originInWindowCoordinates.x, MainWindowController.minTabSpaceWidth)
item.set(width: tabWidth)
}
}
}
Define the set(width:) method in an extension on NSToolbarItem as follows:
private extension NSToolbarItem {
func set(width: CGFloat) {
minSize = .init(width: width, height: minSize.height)
maxSize = .init(width: width, height: maxSize.height)
}
}
Make your window controller conform to NSSplitViewDelegate and assign it to your split view's delegate property.1 Implement the following NSSplitViewDelegate protocol method in your window controller:
override func splitViewDidResizeSubviews(_ notification: Notification) {
adjustTabSpaceWidth()
}
This will yield the desired resizing behavior. (The user will still be able to remove the space completely or reposition it, but he can always add it back to the front.)
1 Note:
If you're using an NSSplitViewController, the system automatically assigns that controller to its split view's delegate property and you cannot change that. As a consequence, you need to subclass NSSplitViewController, override its splitViewDidResizeSubviews() method and notify the window controller from there. Your can achieve that with the following code:
protocol SplitViewControllerDelegate: class {
func splitViewControllerDidResize(_ splitViewController: SplitViewController)
}
class SplitViewController: NSSplitViewController {
weak var delegate: SplitViewControllerDelegate?
override func splitViewDidResizeSubviews(_ notification: Notification) {
delegate?.splitViewControllerDidResize(self)
}
}
Don't forget to assign your window controller as the split view controller's delegate:
override func windowDidLoad() {
super.windowDidLoad()
splitViewController?.delegate = self
}
and to implement the respective delegate method:
extension MainWindowController: SplitViewControllerDelegate {
func splitViewControllerDidResize(_ splitViewController: SplitViewController) {
adjustTabSpaceWidth()
}
}
There is no native way to achieve a "local" toolbar. You would have to create the control yourself, but I believe it would be simpel to make.
Aligning the toolbar items using autolayout is described here. Align with custom toolbar item described by Mischa.
The macOS way is to use the Toolbar solution and make them context sensitive. In this instance the text attribute buttons would enable when the right pane has the focus and disable when it looses the focus.

NSBorderlessWindowMask Subview NSTextfield not keybard editable Swift

I'm using a NSBorderlessWindowMask for my main window on a Swift project (without storyboards), when I load a Subview, the NSTextfield outlet is not keybard editable. I already put this code on the initialisation:
self.window?.makeKeyWindow()
self.window?.becomeKeyWindow()
this allows the outlet to be "blue" like on focus, but the keyboard editing is disabled, i can copy/paste on the textfield
You need to use a custom subclass of NSWindow and override canBecomeKeyWindow() to return true. By default, it returns false for windows without title bars (as documented).
You probably want to do the same for canBecomeMainWindow().
Also, never call becomeKeyWindow() (except to call super in an override). That is called by Cocoa to inform the window that it has become the key window. It does not instruct the window to become the key window.
I found an awesome workaround for this problem:
basically setup at beginning the NSWindow mask as NSTitledWindowMask, when application is loaded, remove set up the new mask NSBorderlessWindowMask
func applicationWillFinishLaunching(notification: NSNotification) {
self.window?.titleVisibility = NSWindowTitleVisibility.Hidden
self.window?.styleMask = NSTitledWindowMask // adds title bar
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
self.window?.makeKeyWindow()
self.window?.becomeKeyWindow()
self.window.setIsVisible(true)
self.window?.styleMask = NSBorderlessWindowMask // removes title bar
}

Cocoa: How to set window title from within view controller in Swift?

I've tried to build on a Cocoa app which uses storyboard and Swift in Xcode 6. However, when I tried to alter the title of window from within NSViewController, the following code doesn't work.
self.title = "changed label"
When I wrote the above code in viewDidLoad() function, the resultant app's title still remains window.
Also, the following code causes an error, since View Controller doesn't have such property as window.
self.window.title = "changed label"
So how can I change the title of window programmatically in Cocoa app which is built on storyboard?
There are 2 problems with your code:
viewDidLoad is called before the view is added to the window
NSViewController does not have a window property
To fix the first one, you could override viewDidAppear(). This method is called after the view has fully transitioned onto the screen. At that point it is already added to a window.
To get a reference to the window title, you can access a view controller's window via its view: self.view.window.title
Just add the following to your view controller subclass, and the window title should change:
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.title = "changed label"
}
This worked for me, currentDict is NSDictionary passed from previous viewController
var currentDict:NSDictionary?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if let myString:String = currentDict?["title"] as? String {
self.title = myString
}
}

In Interface builder, how can I add a custom button to a window title bar?

In my OS X app, using Interface Builder, I have a window that looks like this:
I'd like to add a button to the right-hand side, to achieve this:
If this is possible, how can I do it?
It is not possible to do with Interface Builder, however you can get it done with little bit of coding :
NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; // Get the existing close button of the window. Check documentation for the other window buttons.
NSView *titleBarView = closeButton.superview; // Get the view that encloses that standard window buttons.
NSButton *myButton = …; // Create custom button to be added to the title bar.
myButton.frame = …; // Set the appropriate frame for your button. Use titleBarView.bounds to determine the bounding rect of the view that encloses the standard window buttons.
[titleBarView addSubview:myButton]; // Add the custom button to the title bar.
Swift 2.2 and Auto Layout, Create an "OK" button to the right of the title bar:
let myButton = NSButton()
myButton.title = "OK"
myButton.bezelStyle = .RoundedBezelStyle
let titleBarView = window!.standardWindowButton(.CloseButton)!.superview!
titleBarView.addSubview(myButton)
myButton.translatesAutoresizingMaskIntoConstraints = false
titleBarView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[myButton]-2-|", options: [], metrics: nil, views: ["myButton": myButton]))
titleBarView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-1-[myButton]-3-|", options: [], metrics: nil, views: ["myButton": myButton]))
With auto layout, you don't need to hard-code button's frame. And it is always on the right of the title bar even you resize the window.

Resources