SwiftUI MacOS下窗口开启背景拖动与View添加拖动手

2022-12-27  本文已影响0人  O2Space_Xiu

在使用SwiftUI 开发 MacOS应用(环境swift 5),需要不仅仅状态栏可拖动窗口移动,设置属性window.isMovableByWindowBackground = true,整个窗口背景内容可拖动窗口移动。目前在窗口下的某个View(SwiftUI)添加拖动手势(DragGesture)能在该窗口某个区域内自由拖动,然而发现在单击拖动过程中,只会拖动窗口移动直至窗口顶到桌面顶部时才触发拖动该View。
1、调试发现双击该View进行拖动将避开触发窗口的拖动。
2、同时SwiftUI下一些组件本身区域不会触发窗口背景拖动,比如NavigationLink、Button、List、Table等等,这些组件本身会拦截事件往父视图透传,应该是内部做了处理,SwiftUI无开源无法确定内部逻辑进行效仿。

测试代码

TestDragGestureApp.swift

import SwiftUI

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        if let window = NSApplication.shared.windows.first {
            window.titleVisibility = .hidden
            window.titlebarAppearsTransparent = true
            window.isOpaque = false
            window.backgroundColor = NSColor.white
            window.isMovableByWindowBackground = true // 开启整个窗口背景区域可拖动
        }
    }
}

@main
struct TestDragGestureApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
}

ContentView.swift

import SwiftUI


struct ContentView : View {
    
    @State var offset: CGSize = .zero //表示视图被拖动的距离
    
    var body: some View {
    
        return VStack{
            canMoveView
        }
        .frame(width: 400, height: 400)
    }
    
    private var canMoveView: some View {
        Circle()
            .fill(Color.orange)
            .frame(width: 200, height: 200)
            .offset(offset)
            .gesture(dragGesture)
    }
    
    private var dragGesture: some Gesture {
        DragGesture()
            .onChanged { (value) in
                print(value.startLocation, value.location, value.translation)
                self.offset = value.translation
            }
            .onEnded { (value) in
                
            }
    }
}

上方代码 window.isMovableByWindowBackground设置为true 窗口内任意位置单击可拖动Window,然而视图canMoveView拖动手势失效。

解决方案一

利用SwiftUI Button组件当作canMoveView的容器进行事件拦截

ContentView.swift 改造代码区

    private var canMoveView: some View {
        // 解决方案一
        Button { 

        } label: {
            Circle()
                .fill(Color.orange)
                .frame(width: 200, height: 200)
                .offset(offset)
                .gesture(dragGesture)
        }
        .background(.clear)
        .buttonStyle(.plain)
    }

解决方案二

利用AppKit UI框架,创建NSView子类,重写mouseDownCanMoveWindow Get属性方法返回false,然后将该NSVIew子类转换SwiftUI View,最后将该SwiftUI View设置为目标视图的background视图

新增CannotMoveWindowMonitorView.swift

import SwiftUI

// AppKit UI框架下的NSView
public class CannotMoveWindowMonitorNSView : NSView {
    
    deinit {
        print("deinit(dealloc) ---- CannotMoveWindowMonitorNSView")
    }
    
    public override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        self.wantsLayer = true
        self.layer?.backgroundColor = NSColor.clear.cgColor
        
    }
    
    public override var mouseDownCanMoveWindow: Bool {
        return false
    }
}

// 将 AppKit UI 转换成 SwiftUI
public struct CannotMoveWindowMonitorView : NSViewRepresentable {
    
    public func makeNSView(context: Context) -> CannotMoveWindowMonitorNSView {
        let view = CannotMoveWindowMonitorNSView()
        return view
    }
    
    public func updateNSView(_ nsView: CannotMoveWindowMonitorNSView, context: Context) {
    }
}

新增ViewExtension.swift

import SwiftUI

extension View {
    @ViewBuilder
    public func mouseDownCannotMoveWindow() -> some View {
        self.background(CannotMoveWindowMonitorView())
    }
}

ContentView.swift 改造代码区

    private var canMoveView: some View {
         Circle()
            .fill(Color.orange)
            .frame(width: 200, height: 200)
            .offset(offset)
            .gesture(dragGesture)
            .mouseDownCannotMoveWindow() // 解决方案二
    }

该方案更加SwiftUI化 后面采用只需要添加.mouseDownCannotMoveWindow()即可

后记

1、 注:该问题在SwiftUI iOS下不存在,只存在macOS应用中出现
2、是否SwiftUI官方已经提供扩展方法,查询相关资料未能找捯
3、是否还有其他更好的解决方案,请下方留言

上一篇 下一篇

猜你喜欢

热点阅读