全屏时如何隐藏 SwiftUI 中的工具栏?

问题描述 投票:0回答:3

我有一个 SwiftUI 应用程序,其工具栏包含多个 WindowGroups。我希望除主窗口之外的所有窗口都具有一个行为类似于预览应用程序的工具栏,在全屏时隐藏/折叠工具栏。

我将 .presentedWindowToolbarStyle(.unified) 附加到我的视图,并将 .windowStyle(.titleBar) 附加到我的 WindowGroups。

我尝试过寻找方法,多次询问 ChatGPT,也尝试过寻找带有折叠工具栏的开源 SwiftUI 应用程序,但它们都不起作用,因为它们要么是关于 iOS 的,要么是关于 AppKit 应用程序的。在代码中,我尝试了 .windowStyle 和 .presentedWindowToolbarStyle 的不同设置以及设置 .windowToolbarStyle。

swift macos swiftui toolbar
3个回答
0
投票

这里是有关如何在单击绿色缩放按钮时显示和隐藏 ContentView 工具栏的演示。使用状态变量控制工具栏的可见性。然后使用窗口通知切换该变量。

import SwiftUI
import AppKit

@main
struct MyApp: App {
    @State var isToolbarHidden:Bool = false

    var body: some Scene {
        WindowGroup {
            ContentView()
                .presentedWindowToolbarStyle(.unified)
                .toolbar {
                    if !isToolbarHidden {
                        ToolbarItem(placement: .automatic, content: {
                            Text("Toolfoo Item")
                        })
                    }
                }
                .onAppear {
                    NotificationCenter.default.addObserver(forName: NSWindow.willEnterFullScreenNotification, object: nil, queue: OperationQueue.main, using: { note in
                        self.isToolbarHidden = true
                    })

                    NotificationCenter.default.addObserver(forName: NSWindow.willExitFullScreenNotification, object: nil, queue: OperationQueue.main, using: { note in
                        self.isToolbarHidden = false
                    })
                }
        }
        .windowStyle(.titleBar)
    }
}

0
投票

我有同样的问题大约一个月了,现在才找到解决方案。

NSWindowDelegate
有一个名为
window(_:willUseFullScreenPresentationOptions:)
的方法,它控制进入全屏模式时要使用的演示选项。具体来说,有一个
NSApplication.PresentationOptions.autoHideToolbar
属性可以完全满足您的需求。然而问题是,它存在于
NSWindow
delegate
上,并且在 SwiftUI 中,修改委托并不简单,因为它是由 SwiftUI 控制的。例如,如果您尝试创建自己的
NSWindowDelegate
并将其设置在
NSWindow
上,您会发现某些行为不再有效,例如单击 Dock 中的应用程序图标。

为了避免这种情况,您可以获取视图的

NSWindow
并动态向
NSWindowDelegate
实例添加一个方法。首先,这里有一些代码演示如何获取窗口:

@Observable
class Window {
  // NSWindow needs to be weak to avoid capturing it in views (else, views will not be deallocated, which I've experienced with .onDisappear)
  weak var window: NSWindow?
}

class AppearanceView: NSView {
  var win: Window

  init(window: Window) {
    self.win = window

    super.init(frame: .zero)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidMoveToWindow() {
    super.viewDidMoveToWindow()

    win.window = self.window
  }
}

struct WindowCaptureView: NSViewRepresentable {
  let window: Window

  func makeNSView(context: Context) -> AppearanceView {
    .init(window: window)
  }

  func updateNSView(_ appearanceView: AppearanceView, context: Context) {
    appearanceView.win = window
  }
}

struct WindowViewModifier: ViewModifier {
  @State private var window = Window()

  func body(content: Content) -> some View {
    content
      .environment(window)
      .background {
        WindowCaptureView(window: window)
      }
  }
}

extension View {
  func windowed() -> some View {
    // Don't forget to apply this modifier on your WindowGroup's *view* (note: not *in* `SceneView`, but *on* it)
    self.modifier(WindowViewModifier())
  }
}

现在,对于进入全屏模式时应调用的方法

NSWindowDelegate
,我们首先需要一个实现:

class WindowDelegate {
  @MainActor
  @objc static func window(
    _ window: NSWindow,
    willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []
  ) -> NSApplication.PresentationOptions {
    return proposedOptions.union(.autoHideToolbar)
  }
}

最后,在

WindowGroup
的视图中,我们可以在它出现之前运行一些代码,将方法添加到委托中。

struct SceneView: View {
  @Environment(Window.self) private var win
  private var window: NSWindow? { win.window }

  var body: some View {
    NavigationSplitView {
      Text("Hello,")
    } detail: {
      Text("World!")
    }.task {
      guard let delegate = window?.delegate else {
        return
      }

      // Get the delegate's method in question. The method doesn't seem to be implemented by SwiftUI, which is why addMethod is being used later rather than method_exchangeImplementations.
      let prior = #selector(delegate.window(_:willUseFullScreenPresentationOptions:))
      let selector = #selector(WindowDelegate.window(_:willUseFullScreenPresentationOptions:))
      let method = class_getClassMethod(WindowDelegate.self, selector)!
      let impl = method_getImplementation(method)

      class_addMethod(delegate.superclass, prior, impl, nil)
    }
  }
}

这样,工具栏应该在进入全屏模式时隐藏,并在用户将鼠标移动到屏幕顶部时显示(类似于预览的方式)。此实现的主要问题是它不适用于场景恢复当窗口已处于全屏模式时。用户可以通过退出然后再次进入全屏模式来修复它,但可能有更好的解决方案。


-1
投票

在 SwiftUI 中,当视图处于全屏模式时,您可以使用

edgesIgnoringSafeArea(_:)
修饰符隐藏工具栏。以下是如何实现此目标的示例:

import SwiftUI

struct ContentView: View {
    @State private var isFullScreen = false
    
    var body: some View {
        NavigationView {
            VStack {
                // Your content here
                
                Spacer()
                
                Button(action: {
                    isFullScreen.toggle()
                }) {
                    Text("Toggle Full Screen")
                }
            }
            .navigationBarTitle("Your App")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button(action: {}) {
                        Image(systemName: "gearshape")
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: {}) {
                        Image(systemName: "bell")
                    }
                }
            }
            .edgesIgnoringSafeArea(isFullScreen ? .all : [])
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

在此示例中,

isFullScreen
状态变量用于在全屏模式和正常模式之间切换。点击按钮时,将切换
isFullScreen
值,并将
edgesIgnoringSafeArea(_:)
修改器应用于视图以忽略安全区域边缘,隐藏工具栏。

您可以将

NavigationView
内的内容替换为您自己的内容,并根据您的需求自定义工具栏。

© www.soinside.com 2019 - 2024. All rights reserved.