Created
October 20, 2024 18:48
-
-
Save INCHMAN1900/5025e71f839e234e02e26880978e0dc1 to your computer and use it in GitHub Desktop.
SwiftUI view with window resizing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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