Как реализовать mouseEntered/Exited NSTrackingArea с анимацией?

Я хочу реализовать функцию, которая, когда пользователь наводит курсор на определенную область, появляется новое представление с анимацией в виде ящика. А также, когда пользователь покидает определенную область, ящик должен уйти с анимацией. Это именно то, что вы видите, когда наводите курсор мыши на нижней части экрана в OS X, где Dock появляется и исчезает с анимацией.

Однако, если я реализую функцию с анимацией, она не будет работать должным образом при повторном вводе определенной области перед анимацией в mouseExited: выполнен. Вот мой код:

let trackingArea = NSTrackingArea(rect: CGRectMake(0, 0, 120, 300), options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.MouseEnteredAndExited, owner: self, userInfo: nil)
underView.addTrackingArea(trackingArea) // underView is the dummy view just to respond to the mouse tracking, since the drawerView's frame is changed during the animation; not sure if this is the clean way...

override func mouseEntered(theEvent: NSEvent) {
    let frameAfterVisible = CGRectMake(0, 0, 120, 300)
    NSAnimationContext.runAnimationGroup({
        (context: NSAnimationContext!) in
            context.duration = 0.6
            self.drawerView.animator().frame = frameAfterVisible
        }, completionHandler: { () -> Void in
    })
}

override func mouseExited(theEvent: NSEvent) {
    let frameAfterInvisible = CGRectMake(-120, 0, 120, 300)
    NSAnimationContext.runAnimationGroup({
        (context: NSAnimationContext!) in
            context.duration = 0.6
            self.drawerView.animator().frame = frameAfterInvisible
        }, completionHandler: { () -> Void in
    })
}

// drawerView's frame upon launch is (-120, 0, 120, 300), since it is not visible at first

В этом коде я оживляю drawerView изменив его x позиция. Однако, как я уже говорил, когда вы входите в область отслеживания, а затем покидаете область отслеживания, ящик работает правильно. Но это не тот случай, если вы снова войдете в зону отслеживания до того, как анимация прекращения будет полностью завершена.

Конечно, если я установите продолжительность анимации, например, 0.1это случалось бы редко. Но я хочу переместить вид с анимацией.

Что я хочу сделать, это сделать drawerView начать появляться снова, даже если представление еще не завершилось, исчезают. Есть ли практика, чтобы сделать это?

2 ответа

Решение

У меня есть решение, которое очень похоже на ваш код. Что я делаю иначе, так это то, что я устанавливаю NSTrackingArea не на представлении, которое содержит представление ящика, а на самом представлении ящика.

Это, очевидно, означает, что ящик должен немного "торчать". В моем случае ящик немного виден, когда он опущен, потому что я поместил в него изображение. Если вы этого не хотите, я предлагаю вам оставить видимую область ящика пустой и полупрозрачной.

Вот моя реализация:

private enum DrawerPosition {
    case Up, Down
}

private let DrawerHeightWhenDown: CGFloat = 16
private let DrawerAnimationDuration: NSTimeInterval = 0.75

class ViewController: NSViewController {

    @IBOutlet weak var drawerView: NSImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Remove the auto-constraints for the image view otherwise we are not able to change its position
        view.removeConstraints(view.constraints)
        drawerView.frame = frameForDrawerAtPosition(.Down)

        let trackingArea = NSTrackingArea(rect: drawerView.bounds,
            options: NSTrackingAreaOptions.ActiveInKeyWindow|NSTrackingAreaOptions.MouseEnteredAndExited,
                owner: self, userInfo: nil)
        drawerView.addTrackingArea(trackingArea)
    }

    private func frameForDrawerAtPosition(position: DrawerPosition) -> NSRect {
        var frame = drawerView.frame
        switch position {
        case .Up:
            frame.origin.y = 0
            break
        case .Down:
            frame.origin.y = (-frame.size.height) + DrawerHeightWhenDown
            break
        }
        return frame
    }

    override func mouseEntered(event: NSEvent) {
        NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext!) in
            context.duration = DrawerAnimationDuration
            self.drawerView.animator().frame = self.frameForDrawerAtPosition(.Up)
        }, completionHandler: nil)
    }

    override func mouseExited(theEvent: NSEvent) {
        NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext!) in
            context.duration = DrawerAnimationDuration
            self.drawerView.animator().frame = self.frameForDrawerAtPosition(.Down)
        }, completionHandler: nil)
    }
}

Полный проект на https://github.com/st3fan/Stackru-28777670-TrackingArea

Дайте мне знать, если это было полезно. Рад внести изменения.

Запуск Swift 3. Вам нужно сделать это так:

let trackingArea = NSTrackingArea(rect: CGRectMake(0, 0, 120, 300), options: [NSTrackingAreaOptions.ActiveAlways ,NSTrackingAreaOptions.MouseEnteredAndExited], owner: self, userInfo: nil)
view.addTrackingArea(trackingArea)

Кредиты @ Марк выше!

Другие вопросы по тегам