swiftswiftui

How can I detect when view resizing is finished?


I want to create a solution that tells me when the user has finished resizing a view. Currently, I’m using a timer for this, but I’m not satisfied with my approach because it doesn’t feel like idiomatic SwiftUI coding. I’m looking for a more native way to detect the final size of the view at the end of resizing. As we change the window size our view size would change.

import SwiftUI

struct ContentView: View {
    
    @State private var resizeTimer: Timer? = nil
    
    var body: some View {
        GeometryReader { geometryValue in
            Color.white
                .onChange(of: geometryValue.size) { newValue in

                    // Invalidate any existing timer
                    resizeTimer?.invalidate()

                    // Start a new timer to detect when resizing ends
                    resizeTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) {  _ in // [weak self]
                        didEndResizing(size: newValue)
                    }
                }
        }
        .padding()
    }
    
    private func didEndResizing(size: CGSize) {
        print("View resizing ended. Final size: \(size)")
    }
}

Solution

  • To detect the end of a window resizing session, you can observe the NSWindow.didEndLiveResizeNotification.

    .onReceive(NotificationCenter.default.publisher(for: NSWindow.didEndLiveResizeNotification)) { notification in
        print("Resize did end!")
    }
    

    notification.object tells you which NSWindow it is that got resized, so if your app has multiple windows, you can check that to respond only to resizes of the view's own window. For example:

    struct ContentView: View {
        @State private var myWindow: NSWindow?
        var body: some View {
            Color.blue
                .background { WindowAccessor(window: $myWindow) }
                .onReceive(NotificationCenter.default.publisher(for: NSWindow.didEndLiveResizeNotification)) { notification in
                    if (notification.object as? NSWindow) == myWindow {
                        print("Did resize!")
                    }
                }
        }
    }
    
    struct WindowAccessor: NSViewRepresentable {
        @Binding var window: NSWindow?
        
        class MoveToWindowDetector: NSView {
            var onMoveToWindow: (NSWindow?) -> Void = { _ in }
            
            override func viewDidMoveToWindow() {
                onMoveToWindow(window)
            }
        }
    
        func makeNSView(context: Context) -> MoveToWindowDetector {
            MoveToWindowDetector()
        }
    
        func updateNSView(_ nsView: MoveToWindowDetector, context: Context) {
            nsView.onMoveToWindow = { window = $0 }
        }
    }
    

    WindowAccessor is slightly modified from Asperi's answer here.