2015-02-28 18 views
6

Chcę zaimplementować funkcję polegającą na tym, że gdy użytkownik unosi się nad określonym obszarem, pojawia się nowy widok z animacją przypominającą szufladę. A także, gdy użytkownik opuści określony obszar, szuflada powinna zniknąć z animacją. Dokładnie to widzisz po najechaniu kursorem na dół ekranu w OS X, gdzie pojawia się Dock i znika z animacją.Jak zaimplementować mysz NSTrackingArea?

Jeśli jednak zaimplementuję tę funkcję z animacją, nie będzie ona działać poprawnie po ponownym wejściu do określonego obszaru przed ukończeniem animacji w mouseExited:. Oto mój kod:

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 

W tym kodzie I animowanie drawerView zmieniając jego x pozycję. Jednak, jak już stwierdziłem, po przejściu do obszaru śledzenia, a następnie opuszczeniu obszaru śledzenia, szuflada działa poprawnie. Ale tak nie jest, jeśli ponownie przejdziesz do obszaru śledzenia, zanim animacja zakończenia zostanie w pełni ukończona.

Oczywiście, jeśli ustawię krótszy czas trwania animacji, taki jak 0.1, rzadko to nastąpi. Ale chcę przenieść widok z animacją.

Co chcę zrobić, to początek drawerView pojawiać się ponownie nawet jeśli widok nie zakończyła znikają. Czy jest jakaś praktyka, aby to zrobić?

+0

Czy twoja funkcja 'mouseEntered()' jest wywoływana po ponownym wprowadzeniu podczas animacji? Czy problem polega na tym, że nie jest on wywołany, czy też ustawienie nowej ramki nie jest skuteczne? Wstaw trochę rejestrowania do obu funkcji, przed grupą animacji, na końcu grupy animacji oraz w module obsługi zakończenia. –

Odpowiedz

8

Mam rozwiązanie, które jest bardzo podobne do kodu. To, co robię inaczej, to to, że instaluję NSTrackingArea nie w widoku zawierającym widok szuflady, ale w samym widoku szuflady.

To oczywiście oznacza, że ​​szuflada musi trochę "wystygnąć". W moim przypadku szuflada jest nieznacznie widoczna, gdy jest wyłączona, ponieważ umieszczam w niej obraz. Jeśli tego nie chcesz, proponuję po prostu pozostawić widoczny obszar szuflady pusty i półprzezroczysty.

Oto moja realizacja:

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) 
    } 
} 

Pełny projekt na https://github.com/st3fan/StackOverflow-28777670-TrackingArea

Daj mi znać, jeśli to był użyteczny. Chętnie wprowadzam zmiany.

+9

Należy pamiętać, że od Swift 2.0 trzeba wykonać inną składnię opcji obszaru śledzenia. let trackingArea = NSTrackingArea (rect: self.bounds, options: [NSTrackingAreaOptions.MouseEnteredAndExited, NSTrackingAreaOptions.ActiveAlways], owner: self, userInfo: nil) –

5

Zaczynając Swift 3. Trzeba to zrobić tak:

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

kuponów @marc powyżej!