Skip to content

Instantly share code, notes, and snippets.

@INCHMAN1900
Created October 20, 2024 18:48
Show Gist options
  • Save INCHMAN1900/5025e71f839e234e02e26880978e0dc1 to your computer and use it in GitHub Desktop.
Save INCHMAN1900/5025e71f839e234e02e26880978e0dc1 to your computer and use it in GitHub Desktop.
SwiftUI view with window resizing.
//
// ContentView.swift
// window-demo
//
// Created by Asia Fu on 2024/10/20.
//
import SwiftUI
struct WindowAccessor: NSViewRepresentable {
var onWindowAvailable: (NSWindow) -> Void
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
guard let window = view.window else { return }
onWindowAvailable(window)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
struct ContentView: View {
var titleHeight: CGFloat = 26
@State private var texts: [String] = []
@State private var textWidth: CGFloat = 240
@State private var viewSize: NSSize = .zero
@State private var window: NSWindow? = nil
var body: some View {
VStack {
VStack {
VStack {
ForEach(texts, id: \.self) { text in
HStack {
Text(text)
}
.padding(8)
}
}
.padding(8)
.frame(width: textWidth)
HStack {
Button(action: {
addText()
}) {
Text("Add")
}
Button(action: {
clearTexts()
}) {
Text("Clear")
}
Button(action: {
textWidth += (300 - textWidth) * 2
}) {
Text("Toggle width")
}
}
.padding(.vertical, 12)
}
.background(
GeometryReader { geometry in
Color.clear
.onAppear {
viewSize = geometry.size
setInitialWindowFrame()
}
.onChange(of: geometry.size) {
viewSize = geometry.size
}
}
)
WindowAccessor(onWindowAvailable: { self.window = $0 })
}
.frame(minHeight: 0, idealHeight: 100, alignment: .top)
.background(.yellow.opacity(0.3))
.onChange(of: viewSize.height, {
guard let window = getWindow() else { return }
let targetHeight = viewSize.height + titleHeight
let step = 10
let gap = (targetHeight - window.frame.size.height) / CGFloat(step)
var i = 0
var currentHeight = window.frame.size.height
Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true, block: { timer in
var frameSize = window.frame.size
if i >= 10 {
frameSize.height = targetHeight
timer.invalidate()
} else {
i += 1
currentHeight += gap
frameSize.height = currentHeight
}
// 这里可能会有警告,但是其实 frameSize.height 正常
window.setContentSize(frameSize)
})
})
.onChange(of: viewSize.width) {
guard let window = getWindow() else { return }
var frameSize = window.frame.size
frameSize.width = viewSize.width
window.setContentSize(frameSize)
}
}
private func setInitialWindowFrame() {
guard let window = getWindow() else { return }
var initialSize = viewSize
initialSize.height += titleHeight
window.setContentSize(initialSize)
window.center()
}
// TODO: 这里 window 不一定对,如果有 menubar 的话
private func getWindow() -> NSWindow? {
if let window { return window }
if NSApp.windows.count == 1 {
return NSApp.windows.first
}
return NSApp.keyWindow ?? NSApp.windows.first
}
private func addText() {
withAnimation(.easeOut, { texts.append("Row \(texts.count + 1)") })
}
private func clearTexts() {
withAnimation(.easeOut, { texts.removeAll() })
}
}
#Preview {
ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment