MacOS Catalyst:如何允许双击 UIView 进行缩放 window?

MacOS Catalyst: how to allow double click on UIView to zoom window?

默认情况下,在 MacOS 中,您可以双击 window 的标题栏对其进行缩放(调整大小以适合屏幕 - 不同于最大化)。再次双击它会恢复到原来的大小。

这在我的 Catalyst 应用程序上也能正常工作。但是我需要隐藏标题栏并且需要在该标题栏区域中为我自己的自定义 UIView 提供双击行为。我可以用这个隐藏它:

#if os(OSX) || os(macOS) || targetEnvironment(macCatalyst)
    UIApplication.shared.connectedScenes.forEach({
        if let titlebar = ([=11=] as? UIWindowScene)?.titlebar {
            titlebar.titleVisibility = .hidden
            titlebar.toolbar = nil
        }
    })
#endif

有什么方法可以让我切换 window 缩放吗?

我能够使用解决方法解决这个问题。 UIKit/Catalyst 本身不提供任何方法来做到这一点。但是我能够在 post on

中使用第二种方法概述

How to Access the AppKit API from Mac Catalyst Apps

https://betterprogramming.pub/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5

我使用了第二种方法而不是第一种方法,因为第一种方法似乎是私有的API(我可能是错的)并且会在 App Store 中被拒绝。第二种使用插件包和调用方法的方法对我来说效果很好。这样我不仅可以执行缩放,还可以执行其他 MacOS Appkit 功能,例如侦听键盘、鼠标滚动、悬停检测等。

创建插件包后,这是我在插件中的代码:

Plugin.swift:

import Foundation
@objc(Plugin)
protocol Plugin: NSObjectProtocol {
    init()
    func toggleZoom()
    func macOSStartupStuff()
}

MacPlugin.swift:

import AppKit
class MacPlugin: NSObject, Plugin {
    required override init() {}

    func macOSStartupStuff() {
        NSApplication.shared.windows.forEach({
            [=11=].titlebarAppearsTransparent = true
            [=11=].titleVisibility = .hidden
            [=11=].backgroundColor = .clear
            ([=11=].contentView?.superview?.allSubviews.first(where: { String(describing: type(of: [=11=])).hasSuffix("TitlebarDecorationView") }))?.alphaValue = 0
        })
    }

    func toggleZoom(){
        NSApplication.shared.windows.forEach({
            [=11=].performZoom(nil)
        })
    }
}

extension NSView {
    var allSubviews: [NSView] {
        return subviews.flatMap { [[=11=]] + [=11=].allSubviews }
    }
}

然后我从我的 iOS 应用程序代码中调用它。这会在顶部添加一个透明视图,双击会调用用于切换缩放的插件代码。

请注意,当 windows 已初始化并呈现时,您必须从 viewDidAppear 或某处调用它。不然不行。

#if os(OSX) || os(macOS) || targetEnvironment(macCatalyst)
    @objc func zoomTapped(){
        plugin?.toggleZoom()
    }
    
    var pluginWasLoaded = false
    lazy var plugin : Plugin? = {
        pluginWasLoaded = true
        if let window = (UIApplication.shared.delegate as? AppDelegate)?.window {
            let transparentTitleBarForDoubleClick = UIView(frame: .zero)
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(zoomTapped))
            tapGesture.numberOfTapsRequired = 2
            transparentTitleBarForDoubleClick.addGestureRecognizer(tapGesture)
            transparentTitleBarForDoubleClick.isUserInteractionEnabled = true
            
            transparentTitleBarForDoubleClick.backgroundColor = .clear
            transparentTitleBarForDoubleClick.translatesAutoresizingMaskIntoConstraints = false
            window.addSubview(transparentTitleBarForDoubleClick)
            window.bringSubviewToFront(transparentTitleBarForDoubleClick)
            
            window.addConstraints([
                                NSLayoutConstraint(item: transparentTitleBarForDoubleClick, attribute: .leading, relatedBy: .equal, toItem: window, attribute: .leading, multiplier: 1, constant: 0),
                                 NSLayoutConstraint(item: transparentTitleBarForDoubleClick, attribute: .top, relatedBy: .equal, toItem: window, attribute: .top, multiplier: 1, constant: 0),
                                 NSLayoutConstraint(item: transparentTitleBarForDoubleClick, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1, constant: 0),
                                 transparentTitleBarForDoubleClick.bottomAnchor.constraint(equalTo: window.safeTopAnchor)
                                 ])
            window.layoutIfNeeded()
        }
        
        guard let bundleURL = Bundle.main.builtInPlugInsURL?.appendingPathComponent("MacPlugin.bundle") else { return nil }
        guard let bundle = Bundle(url: bundleURL) else { return nil }
        guard let pluginClass = bundle.classNamed("MacPlugin.MacPlugin") as? Plugin.Type else { return nil }
        return pluginClass.init()
    }()
#endif

从 viewDidAppear 调用它:

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        #if os(OSX) || os(macOS) || targetEnvironment(macCatalyst)
        if !Singleton.shared.pluginWasLoaded {
            Singleton.shared.plugin?.macOSStartupStuff()
        }
        #endif
}