Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to put SwiftUI button into NSToolbar?

This is how SwiftUI can be inserted into NSToolbar using an accessory view controller:

import SwiftUI
import PlaygroundSupport

var hostingView = NSHostingView(rootView:
  ZStack {
    Color.clear
    HStack {
      Text("Hey")
      Text("SwiftUI")
    }
  }
  .padding()
  .edgesIgnoringSafeArea(.all)
)
hostingView.frame.size = hostingView.fittingSize

let titlebarAccessory = NSTitlebarAccessoryViewController()
titlebarAccessory.view = hostingView
titlebarAccessory.layoutAttribute = .trailing

let mask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
let window = NSWindow(
  contentRect: .init(x: 0, y: 0, width: 480, height: 300),
  styleMask: mask, backing: .buffered, defer: false)
window.center()
window.contentView = NSHostingView(rootView: Color(.windowBackgroundColor))
window.toolbar = .init()
window.titleVisibility = .hidden
window.addTitlebarAccessoryViewController(titlebarAccessory)

PlaygroundPage.current.liveView = window.contentView?.superview

The code above does work:

If we insert a button however:

HStack {
  Text("Hey")
  Button(action: {}) {
    Text("SwiftUI")
  }
}

It would not work as expected:

Any suggestions?

P. S. This is a working solution:

HStack {
  Text("Hey")
    .offset(x: 0, y: -1)
  Button(action: {}) {
    Text("SwiftUI")
      .offset(x: 0, y: -7)
  }
}
.font(.caption)

like image 945
Vadim Avatar asked Oct 23 '19 08:10

Vadim


2 Answers

The way I eventually handled it (making it work also when the user makes the window full screen AND regardless of the height* of the content) is below. Note that I'm using Toolbar view in an NSHostingView within NSTitlebarAccessoryViewController of a macOS app built with most recent Xcode version (obviously, on macOS Catalina).

Important things: *you still need to setup a fixed height for the frame (but at least you won't rely to the "variable" -7) and offset to top by half of the actual height ONLY when there is safe area top inset (in full screen mode, apparently, it isn't) and therefore GeometryReader is a must:

struct Toolbar : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("1st row")
                Text("2nd row")
            }
            .offset(y: geometry.safeAreaInsets.top > 0 ? -geometry.size.height / 2 : 0)
        }
        .frame(height: 42)
    }
}

and where window is created:

let toolbar = NSHostingView(rootView: Toolbar())
toolbar.frame.size = toolbar.fittingSize
let toolbarController = NSTitlebarAccessoryViewController()
toolbarController.view = toolbar
window.addTitlebarAccessoryViewController(toolbarController)
like image 128
Sorin Dolha Avatar answered Nov 14 '22 08:11

Sorin Dolha


Posting my comment as an answer per request. Not necessarily a reliable solution but as a workaround you can use

Text("SwiftUI")
  .padding(EdgeInsets(top: -7, leading: 0, bottom: 0, trailing: 0))

or .offset on the Text in the button. No guarantees on how long that will last as a solution.

like image 1
Lucas Derraugh Avatar answered Nov 14 '22 06:11

Lucas Derraugh