如何从整个应用程序的弹出窗口中拖动按钮? (swift)

How to drag a button from a popover across whole app? (swift)

在我的应用程序中,我显示了一个弹出窗口,其中显示了一个自定义 UIView,允许用户使用各种滑块 select 一种颜色。我还想实现一个 'eyedropper' 工具,用户可以点击并按住该工具然后四处拖动以从应用程序中可见的任何内容中选择一种颜色。在我的自定义 UIView 中,我向指向 handlePan 方法的按钮添加了一个 UIPanGestureRecognizer

var eyedropperStartLocation = CGPoint.zero
@objc func handlePan(recognizer: UIPanGestureRecognizer) {

    // self is a custom UIView that contains my color selection
    // sliders and is placed inside a UITableView that's in
    // the popover.
    let translation = recognizer.translation(in: self)
    if let view = recognizer.view {

        switch recognizer.state {
        case .began:
            eyedropperStartLocation = view.center

        case .ended, .failed, .cancelled:
            view.center = eyedropperStartLocation
            return

        default: break

        }
        view.center = CGPoint(x: view.center.x + translation.x,
                              y: view.center.y + translation.y)
        recognizer.setTranslation(CGPoint.zero, in: self)

    }
}

我可以拖动按钮,它会改变位置,但是我有两个问题:

  1. 吸管按钮并不总是在其他项目的前面,即使在弹出框内或弹出框内的自定义 UIView 内也是如此
  2. 滴管按钮在弹出窗口的边界外消失

如何让按钮始终可见,包括在弹出窗口之外?我想检测用户在应用程序中放开它的位置,以便确定它的颜色。

我想出了如何做到这一点,所以我会回答我自己的问题。我没有在弹出窗口内的 view/button 周围移动,而是创建了一个新的 UIImageView 并将其添加到应用程序的 Window,让它跨越整个应用程序。原始按钮保留在原处 - 您可以轻松更改其状态以使其看起来不同,或者如果需要则将其隐藏。

您也可以使用 Interface Builder 绑定到@IBActions,但我只是在代码中完成了所有操作。 clickButton 方法开始了,但计算 window 中的位置并将其放在屏幕上。 handlePan 方法进行翻译并允许您移动它。

下面的所有代码都是 swift 4 并且适用于 XCode 9.4.1(假设我没有引入任何拼写错误):

// Call this from all your init methods so it will always happen
func commonInit() {
    let panner = UIPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:)))
    theButton.addGestureRecognizer(panner)
    theButton.addTarget(self, action: #selector(clickButton), for: .touchDown)
        eyedropperButton.addTarget(self, action: #selector(unclickButton), for: .touchUpInside)
        eyedropperButton.addTarget(self, action: #selector(unclickButton), for: .touchUpOutside)
}

var startLocation = CGPoint.zero
lazy var floatingView: UIImageView = {
    let view = UIImageView(image: UIImage(named: "imagename"))
    view.backgroundColor = UIColor.blue
    return view
}()


// When the user clicks button - we create the image and put it on the screen
// this makes the action seem faster vs waiting for the panning action to kick in
@objc func clickButton() {

    guard let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window else { return }

    // We ask the button what it's bounds are within it's own coordinate system and convert that to the
    // Window's coordinate system and set the frame on the floating view. This makes the new view overlap the
    // button exactly.
    floatingView.frame = theButton.convert(theButton.bounds, to: nil)

    window.addSubview(floatingView)

    // Save the location so we can translate it as part of the pan actions
    startLocation = floatingView.center
}

// This is here to handle the case where the user didn't move enough to kick in the panGestureRecognizer and cancel the action
@objc func unclickButton() {
    floatingView.removeFromSuperview()
}

@objc func handlePan(recognizer: UIPanGestureRecognizer) {
    let translation = recognizer.translation(in: self)

    switch recognizer.state {
    case .ended, .failed, .cancelled:
        doSomething()
        floatingView.removeFromSuperview()
        return
    default: break

    }

    // This section is called for any pan state except .ended, .failed and .cancelled
    floatingView.center = CGPoint(x: floatingView.center.x + translation.x,
                                  y: floatingView.center.y + translation.y)
    recognizer.setTranslation(CGPoint.zero, in: self)
}