Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CGAffineTransforms performance is really slow on iOS 7

At the moment I'm creating some transitions and transform via CGAffineTransform for a panning view and I'm running in troubles because of the transform performance under iOS 7 and an iPhone 4.

I dived in Istruments and logged the stuff and the heavy lifting is done when I'm applying my transforms to the view.

Current Implementation

func handlePan(recognizer : UIPanGestureRecognizer) {

        let drawerLocation = recognizer.locationInView(drawerView!)
        let locationInView = recognizer.locationInView(containerView!)
        let progressMax = containerView!.frame.height - 40 - 20

        if(recognizer.state == .Changed) {

            let offsetDrag = dragStartPosition.y - locationInView.y
            let progress = Float(offsetDrag / progressMax)

            if(offsetDrag >= 0) {

                let positionTransform = CGAffineTransformMakeTranslation(0, -((containerView!.bounds.height - 40 - 20) * CGFloat(normalizedProgress)))
                viewWithTransform.transform = positionTransform // really bad performance here
            } else {
                // reset the transition
            }     
    }
}

Workaround for iOS 7

func handlePan(recognizer : UIPanGestureRecognizer) {

        let drawerLocation = recognizer.locationInView(drawerView!)
        let locationInView = recognizer.locationInView(containerView!)
        let progressMax = containerView!.frame.height - 40 - 20

        if(recognizer.state == .Changed) {

            let offsetDrag = dragStartPosition.y - locationInView.y
            let progress = Float(offsetDrag / progressMax)

            if(offsetDrag >= 0) {
                if UIDevice.currentDevice().systemMajorVersion() > 7 {
                    let positionTransform = CGAffineTransformMakeTranslation(0, -((containerView!.bounds.height - 40 - 20) * CGFloat(progress)))
                    viewWithTransform.transform = positionTransform // really bad performance here
                } else {
                    viewWithTransform.frame = CGRectMake(0, -((containerView!.bounds.height - 40 - 20) * CGFloat(progress)), drawerView!.frame.size.width, drawerView!.frame.size.height); // works like a charm on iOS 7
                }

            } else {
                // reset the transition
            }     
    }
}

Question

Why is the performance so bad on iOS 7 and my iPhone 4 with CGAffineTransforms? Because it's doing the same thing with the offset then the frame setting in the workaround. When I use UIView.animateWithDuration() with transform it's performing on 60fps. What can I do not to rewrite the whole implementation on my iOS 7 basis?

UPDATE 28th July Found out that AutoLayout is possible involved in this issue. Here is a TimeProfiler Stack from my current calls:

Time Profiler

Now I'm facing a big problem in my current implementation, because I rely on AutoLayout. What's the easiest solution to solve this hassle on iOS 7?

like image 588
mariusLAN Avatar asked Jun 19 '15 06:06

mariusLAN


1 Answers

While you are right that they do the same thing, under the hood, it is not that easy - there are matrix multiplications going all over the place. More on that can be found here.

It is strange that if you are doing just this, it affects your performance - but I guess your layout is complicated and so re-rendering takes a lot of time ; I actually had the same problem like a week ago, so here is what helped me:

  • removing AutoLayout from that particular view for some reason helps a lot
  • manipulation with frame should be used when you are not rotating / scaling view. It really becomes need when you are using those.
  • removing shadows and semi-transparent views, especially if there is more of them behind each other, REALLY drops the FPS when transform is used

Also, you can try to assign that transform in async call and see if that helps:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            // Transform view
        })
    })

And if you really want to be fancy, use POP Framework from Facebook. It is great for what you want, and it allows you to do some fancy stuff like bounce, springiness etc. Here is how you can use it:

// Create spring animation (there are more types, if you want)
let animation = POPSpringAnimation(propertyNamed: kPOPViewCenter)

// Configure it properly
animation.autoreverses = false
animation.removedOnCompletion = true
animation.fromValue = view.center
animation.toValue = finalPositionPoint

// Add new animation to your view - animation key can be whatever you like, serves as reference
view.pop_addAnimation(animation, forKey: "YourAnimationKey")

Edit: If you are just moving view around, use .center property, not the frame. It saves you need to define height / width again and gives clearer idea about your intentions.

like image 174
Jiri Trecak Avatar answered Nov 15 '22 03:11

Jiri Trecak