在使用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、是否还有其他更好的解决方案,请下方留言
网友评论