You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS菜单栏应用:如何将内购系统弹窗定位到NSPopover?

让macOS内购弹窗居中显示在自定义窗口上

StoreKit 并未提供类似 Sign in with Apple 的 presentationContextProvider 接口来指定内购弹窗的展示锚点,但可以通过以下几种方式实现需求:

方法1:监听并调整系统内购窗口位置

系统内购弹窗本质是 NSWindow 实例,我们可以通过监听窗口显示通知,识别目标弹窗后调整其位置:

import StoreKit
import AppKit

class PurchaseHandler: NSObject, SKPaymentTransactionObserver {
    override init() {
        super.init()
        SKPaymentQueue.default().add(self)
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(onWindowVisible(_:)),
            name: NSWindow.didBecomeVisibleNotification,
            object: nil
        )
    }

    @objc private func onWindowVisible(_ notification: Notification) {
        guard let window = notification.object as? NSWindow else { return }
        
        // 通过标题或类名识别内购窗口(注意:类名可能随系统版本变化)
        if window.title == "购买" || String(describing: type(of: window)).contains("StoreKit") {
            // 替换为你的目标窗口(比如NSPopover的窗口)
            guard let targetWindow = NSApp.windows.first(where: { $0 is NSPopoverWindow }) else { return }
            
            let targetCenter = targetWindow.frame.center
            let newFrame = NSRect(
                x: targetCenter.x - window.frame.width / 2,
                y: targetCenter.y - window.frame.height / 2,
                width: window.frame.width,
                height: window.frame.height
            )
            
            window.setFrame(newFrame, display: true, animate: true)
        }
    }

    // MARK: SKPaymentTransactionObserver
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        // 处理交易状态更新
    }
}

extension NSRect {
    var center: NSPoint {
        NSPoint(x: origin.x + size.width / 2, y: origin.y + size.height / 2)
    }
}

注意:

  • 依赖窗口标题或类名识别弹窗存在兼容性风险,不同macOS版本可能有变化
  • 该方法属于hack手段,有被App Store审核拒绝的可能,仅建议测试环境使用

方法2:自定义内购界面(推荐)

更合规的方案是放弃系统弹窗,使用自定义UI完全控制购买流程:

  • 设计自定义购买确认弹窗,直接居中显示在你的NSPopover窗口上
  • 用户确认后调用 SKPaymentQueue.default().add(payment) 发起支付
  • 通过 SKPaymentTransactionObserver 监听交易状态,在自定义界面中反馈结果

这种方式完全符合Apple的审核规范,且能100%控制界面样式与位置。

方法3:借助Accessibility API调整窗口

如果必须使用系统弹窗,可通过Accessibility API定位并移动窗口,但需要用户授权辅助功能权限:

import AppKit
import Accessibility

func adjustStoreKitWindow(to targetWindow: NSWindow) {
    guard let frontApp = NSWorkspace.shared.frontmostApplication, let pid = frontApp.processIdentifier else { return }
    
    let appElement = AXUIElementCreateApplication(pid)
    var windows: AnyObject?
    AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windows)
    
    guard let windowList = windows as? [AXUIElement] else { return }
    for window in windowList {
        var title: AnyObject?
        AXUIElementCopyAttributeValue(window, kAXTitleAttribute as CFString, &title)
        
        if let windowTitle = title as? String, windowTitle == "购买" {
            let targetCenter = targetWindow.frame.center
            let newPosition = CGPoint(
                x: targetCenter.x - window.frame.width / 2,
                y: targetCenter.y - window.frame.height / 2
            )
            AXUIElementSetAttributeValue(window, kAXPositionAttribute as CFString, newPosition as CFTypeRef)
        }
    }
}

注意:

  • 应用需要请求辅助功能权限,用户授权后才能生效
  • 需向Apple说明使用Accessibility API的必要性,避免审核问题

内容的提问来源于stack exchange,提问作者Peter R

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:27:49