Tana Gone
Tana Gone
1 min read

Categories

SwiftUIはmacOSアプリのWindow Sizeを変更する手段を提供していない。ViewのLayoutの制御手段が主でWindow管理は別物だ。View.frameモディファイアでWindow Sizeを制御出来るようにも思えるがやってみると上手く行かない。

AppKitのNSWindowにはNSWindow#setFrameがあってWindow Sizeを制御できる。NSWindowインスタンスをContentViewが取得できればNSWindowインスタンスに発生するイベントをdelegate methodで捕まえることもできる。 そこでApp生成後に作られるScene(ContentView)のonAppearモディファイアでNSWindowインスタンス(window)を取得する。window.delegateが受け取るNotificationにはNSWindowインスタンスが入っている。NotificationをContentViewでも同時に受け取る事によりNSWindowインスタンスをContentViewから操作でき、つまりWindow Sizeを変更できる。

  • 画像はDefault Sizeボタンを押下することでWindow Sizeを300x300にするアプリのデモ映像

defaultSize_M

  • App生成コード

    import SwiftUI
      
    @main
    struct SimpleWindowApp: App {
      @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
      
      var body: some Scene {
        WindowGroup {
          ContentView()
            .onAppear {
              // 現在のウィンドウの取得とデリゲート設定
              if let window = NSApplication.shared.windows.first {
                window.delegate = appDelegate
              }
            }
        }
      }
    }
      
    class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
      func windowDidBecomeMain(_ notification: Notification) {
        if let window = notification.object as? NSWindow {
    //      print("ウィンドウがメインになりました: \(window.title)")
          print("ウィンドウがメインになりました: \(window.windowNumber)")
        }
      }
      
      func windowDidChangeTabbingMode(_ notification: Notification) {
        print("windowDidChangeTabbingMode: ")
        if let window = notification.object as? NSWindow {
          if window.tabbingMode == .disallowed {
            print("ウィンドウが分離されました: \(window.windowNumber)")
          } else {
            print("ウィンドウが再度タブに追加されました: \(window.windowNumber)")
          }
        }
      }
      
      func windowDidResignMain(_ notification: Notification) {
        if let window = notification.object as? NSWindow {
          print("ウィンドウがメインから離れました: \(window.windowNumber)")
        }
      }
      
      func windowWillClose(_ notification: Notification) {
        if let window = notification.object as? NSWindow {
          print("ウィンドウが閉じられました: \(window.windowNumber)")
      
        }
      }
    }
    
  • ContentViewのコード

    import SwiftUI
      
    struct ContentView: View {
      @State var windowNumber: Int!
      var body: some View {
        VStack {
          Image(systemName: "globe")
            .imageScale(.large)
            .foregroundStyle(.tint)
          Text("Window #: \(windowNumber ?? 0)")
          Button("Default Size") {
            // Get the window whose window number is windowNumber
            if let window = NSApplication.shared.windows.first(
                  where: { $0.windowNumber == windowNumber }) {
              let currentFrame = window.frame
              let newFrame = NSRect(x: currentFrame.minX, y: currentFrame.minY,
                                    width: 300, height: 300)
              window.setFrame(newFrame, display: true)
            }
          }
        }
        .onReceive(NotificationCenter.default.publisher(
          for: NSWindow.didResizeNotification)) { e in
            if let window = e.object as? NSWindow {
              let contentRect = window.contentLayoutRect
              if windowNumber == nil {
                windowNumber = window.windowNumber
              }
              // windowNumber = window.windowNumber
              print("Window resized - content size: \(contentRect.size), Window #: \(window.windowNumber)")
            }
          }
          .onReceive(NotificationCenter.default.publisher(
            for: NSWindow.didBecomeMainNotification)) { e in
              if let window = e.object as? NSWindow {
                let contentRect = window.contentLayoutRect
                if windowNumber == nil {
                  windowNumber = window.windowNumber
                }
                // windowNumber = window.windowNumber
                print("Become Main - content size: \(contentRect.size), Window #: \(window.windowNumber)")
              }
            }
          .padding()
      }
    }
    
  • note: windowNumberが0となる事がある。そこでResizeイベントでもwindowNumberセット defaultSize_2
  • note: それでもWindowの数が0の時にcommand-NでWindowを生成すると古いwindowNumberが邪魔して0となる。解決策は?