mardi 22 mars 2016

Where should custom, reusable animation code go?

Overview: I currently have a custom UIView subclass that implements custom animation logic, and I'm not sure that the view class is the best place to put that code.

I'm making an iOS app in Swift that uses a UIView subclass I call DoorView. A DoorView represents a sliding door which, in response to a swipe gesture, performs a sliding animation to open.

Here's a complete animation as I have it now:

enter image description here

In trying to keep my View Controller light, I put the actual core animation code that handles these animations into my DoorView class. My View Controller handles the gesture by checking if it matches the gesture required to open a given door, and if so, calls an open() method on the DoorView.

So in ViewController:

@IBAction func swipe(sender: UISwipeGestureRecognizer) {
        if (sender.direction.rawValue == currentDoorView.door.swipeDirection.rawValue) {
            self.currentDoorView.open()
        }
    }

And here's the open() method in my DoorView class: Note: this is just the sliding animation, and the check for a Sliding-type will be used in the future to differentiate from other types of doors (e.g. hinged).

func open(withDuration duration: CFTimeInterval = 1.0) {

    /// We only slideOpen if switch statement below determines self.door is Sliding,
    /// so we downcast as SlidingDoor so we can switch on door.slideDirection.
    /// For each slideDirection case, a different translation is created,
    /// which is then passed into the slideAnimation below.
    func slideOpen() {

        let slidingDoor = self.door as! SlidingDoor
        let translation: CATransform3D

        switch slidingDoor.slideDirection {
        case .Down:
            let height = baseLayer.bounds.height
            translation = CATransform3DMakeTranslation(0, height, 0)
        case .Left:
            let width = openingLayer.bounds.size.width
            translation = CATransform3DMakeTranslation(-width, 0, 0)
        case .Right:
            let width = openingLayer.bounds.size.width
            translation = CATransform3DMakeTranslation(width, 0, 0)
        case .Up:
            let height = baseLayer.bounds.height
            translation = CATransform3DMakeTranslation(0, -height, 0)
        }

        let slideAnimation = {
            (completion:(() -> ())?) in
            CATransaction.begin()
            CATransaction.setCompletionBlock(completion)
            CATransaction.setAnimationDuration(duration)
            self.openingLayer.transform = translation
            CATransaction.commit()
        }

        /// Actual call to slideAnimation closure. 
        /// Upon completion, notify delegate and call walkThroughDoor()
        slideAnimation({
            self.delegate?.doorDidOpen(self)
            self.walkThroughDoor()
        })
    }

    /// Switch to determine door type, and thus appropriate opening animation.
    switch self.door {
    case is Sliding:
        slideOpen()
    default:
        print("is not sliding")
    }
}

So is it okay to put animation logic in a view class? It was my first instinct because a) these animations are specific to my DoorView, and b) because animateWithDuration is a class method of UIView, so there seems to be some precedent for animations to be handled by views/view classes themselves.

But I continue to develop my app, I will be adding more door types with their own animations, and I fear DoorView will grow too fat with animation code. Should I at that point simply start making DoorView subclasses (i.e. SlidingDoorView, HingedDoorView, etc.)?

Or should view animations be handled by the view controller? My problem with that, besides VC bloat, is that if I want to use DoorViews in other view controllers I'll need to duplicate code. This way, my DoorViews come packaged with their own animations and all my VCs need to do is call open().

Aucun commentaire:

Enregistrer un commentaire