SwiftUI和三指撤销手势

5

我正在为iOS的SwiftUI应用程序实现撤销功能,但是我无法使撤销手势工作。以下是一个演示问题的示例:

class Model: ObservableObject {
    @Published var active = false

    func registerUndo(_ newValue: Bool, in undoManager: UndoManager?) {
        let oldValue = active
        undoManager?.registerUndo(withTarget: self) { target in
            target.active = oldValue
        }
        active = newValue
    }
}

struct UndoTest: View {
    @ObservedObject private var model = Model()
    @Environment(\.undoManager) var undoManager

    var body: some View {
        VStack {
            Toggle(isOn: Binding<Bool>(
                get: { self.model.active },
                set: { self.model.registerUndo($0, in: self.undoManager) }
            )) {
                Text("Toggle")
            }
            .frame(width: 120)
            Button(action: {
                self.undoManager?.undo()
            }, label: {
                Text("Undo")
                    .foregroundColor(.white)
                    .padding()
                    .background(self.undoManager?.canUndo == true ? Color.blue : Color.gray)
            })
        }
    }
}

切换开关并点击撤销按钮可以正常工作。使用三指撤销手势或者晃动撤销都不起作用。如何与系统手势相结合?

1个回答

6

看起来编辑手势需要窗口有第一响应者,并且SwiftUI默认没有设置任何UIWindow想要选择的第一响应者。

如果您子类化UIHostingController,并在子类中重写canBecomeFirstResponder以返回true,那么UIWindow会将您的控制器设置为默认的第一响应者,这似乎足以启用编辑手势。

我在运行iPadOS 13.1 beta 2 (17A5831c)的iPad Pro上测试了以下代码。大部分都能正常工作。我认为有一个iOS bug,可能在更新的beta版本中得到修复:当撤销栈为空时,手势有时不起作用(即使可以执行重做操作)。切换到主屏幕,然后再返回测试应用程序(而不杀死测试应用程序)似乎可以使编辑手势再次起作用。

import UIKit
import SwiftUI

class MyHostingController<Content: View>: UIHostingController<Content> {
    override var canBecomeFirstResponder: Bool { true }
}

class Model: ObservableObject {
    init(undoManager: UndoManager) {
        self.undoManager = undoManager
    }

    let undoManager: UndoManager

    @Published var active = false {
        willSet {
            let oldValue = active
            undoManager.registerUndo(withTarget: self) { me in
                me.active = oldValue
            }
        }
    }
}

struct ContentView: View {
    @ObservedObject var model: Model
    @Environment(\.undoManager) var undoManager

    var body: some View {
        VStack {
            Toggle("Active", isOn: $model.active)
                .frame(width: 120)
            HStack {
                Button("Undo") {
                    withAnimation {
                        self.undoManager?.undo()
                    }
                }.disabled(!(undoManager?.canUndo ?? false))
                Button("Redo") {
                    withAnimation {
                        self.undoManager?.redo()
                    }
                }.disabled(!(undoManager?.canRedo ?? false))
            }
        }
    }
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = scene as? UIWindowScene else { return }

        let window = UIWindow(windowScene: scene)
        let model = Model(undoManager: window.undoManager!)
        let contentView = ContentView(model: model)

        window.rootViewController = MyHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
}

太好了!我验证了一下,通过将托管控制器设置为第一响应者,它的撤销管理器现在成为环境中的管理器。非常方便。我还确认了一个错误,即当撤销堆栈为空时,重做不起作用(报告:FB7269279)。请注意,即使三指手势无法使用,摇晃以撤消仍然可以继续工作。 - smr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接