Reputation: 4836
I have a SwiftUI view that do not know the size of because of variable length strings (e.g. due to localisation). I need to have the containing NSWindow
resize to fix the content. I can provide a fixed/minimum width.
Creating a new Mac app with an AppKit delegate yields a fairly simple AppDelegate:
import Cocoa
import SwiftUI
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
}
I then have a SwiftUI with a button that adds to the label's text to simulate changing the text:
import SwiftUI
struct ContentView: View {
@State var text = "Hello, World!"
var body: some View {
VStack {
Text(text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Button("Add Some Text") {
text += "\nHello, World!"
}
}
}
}
Clicking the button eventually causes too many lines of text to be added and the label truncates.
How can I update the container NSWindow
's content size when the size of the SwiftUI view changes?
Upvotes: 9
Views: 3248
Reputation: 155
100% working example for my Safari extension "JS Blocker" with popup (AppKit XIB Popup + SwiftUI markup):
import SafariServices
import SwiftUI
struct Popup: View {
// !!! only Popup itself determines its size - not its container !!!
static let FRAME_SIZE_W: CGFloat = 450
static let FRAME_SIZE_H: CGFloat = 450
var body: some View {
VStack(spacing: 0) {
Text("POPUP")
}.frame(width: Popup.FRAME_SIZE_W, height: Popup.FRAME_SIZE_H)
}
}
class PopupViewController: SFSafariExtensionViewController {
static var popupShared: Popup = {
return Popup()
}()
var popupHost: NSHostingController<Popup>? = nil
var popupView: NSView? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.popupHost = NSHostingController(rootView: PopupViewController.popupShared)
self.popupView = self.popupHost!.view
self.view.addSubview(self.popupView!)
}
// !!! magic is here !!!
override func viewDidAppear() {
self.popupView!.frame = CGRect(x: 0, y: 0, width: Int(self.popupView!.intrinsicContentSize.width), height: Int(self.popupView!.intrinsicContentSize.height))
self.preferredContentSize = CGSize( width: Int(self.popupView!.intrinsicContentSize.width), height: Int(self.popupView!.intrinsicContentSize.height))
}
}
Upvotes: 0
Reputation: 258345
As far as I understood you need the following (tested with Xcode 12.1)
struct ContentView: View {
@State var text = "Hello, World!"
var body: some View {
VStack {
Text(text)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Button("Add Some Text") {
text += "\nHello, World!"
}
}
.frame(minWidth:480, minHeight: 300) // << this for default
.fixedSize() // << this to update container
}
}
Upvotes: 15