如何修复未提交的CATransaction警告

时间:2014-12-18 06:04:47

标签: macos core-animation

我使用自定义NSView创建了一个NSProgressIndicator Spinner,如下所示,但是下面还会显示未提交的CATransaction错误。

我认为这是因为用于Spinner动画的后台线程。谁能告诉我:

a)使用后台线程的spinner代码是否有问题,如果有,那么替代方案是什么? b)如何防止CATransaction错误?

//
//  Spinner.swift
//  Sample
//
//  Created by Duncan Groenewald on 18/12/2014.
//  Copyright (c) 2014 Duncan Groenewald. All rights reserved.
//

import Cocoa


class Spinner: NSView {
    // Some constants to control the animation
    let kAlphaWhenStopped: CGFloat =  0.15
    let kFadeMultiplier: CGFloat  =  0.85

    var position = 0
    var numFins: Int = 12
    var finColors = Array<NSColor>()
    //NSColor *_finColors;

    dynamic var animate: Bool = false {
        didSet {
            //FLog("called")
            if animate {
                startAnimation(self)
            } else {
                stopAnimation(self)
            }
        }
    }

    var isAnimating: Bool = false
    var isFadingOut: Bool = false
    var animationTimer: NSTimer? = nil
    var animationThread: NSThread? = nil

    var foreColor = NSColor.whiteColor()
    var backColor = NSColor.yellowColor()

    var displayedWhenStopped: Bool = true

    // For determinate mode
    var indeterminate: Bool = true
    var currentValue: Double = 0.0

    var color: NSColor? = nil
    var backgroundColor = NSColor.clearColor()

    var drawsBackground: Bool = true
    var usesThreadedAnimation: Bool = true
    var isIndeterminate: Bool = true
    //var doubleValue: Double = 0.0
    var maxValue: Double = 0.0

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)

        commonInit()
    }


    func commonInit() {
        position = 0;
        numFins = 12;

        isAnimating = false
        isFadingOut = false

        loadColors()

        backColor = NSColor.clearColor()
        drawsBackground = false


        displayedWhenStopped = true
        usesThreadedAnimation = true

        indeterminate = true
        currentValue = 0.0;
        maxValue = 100.0;
    }

    func loadColors() {
        //FLog("called")
        for var i=0; i<numFins; i++ {
            finColors.append(foreColor)

        }
    }
    override func viewDidMoveToWindow() {
        super.viewDidMoveToWindow()
        //FLog(" called")

        if self.hidden {
            self.actuallyStopAnimation()
        } else if isAnimating {
            self.actuallyStartAnimation()
        }

    }

    func startAnimation(sender: AnyObject?)
    {
        //FLog(" called")
        if !indeterminate {
            return
        }
        if isAnimating && !isFadingOut {
            return
        }

        self.actuallyStartAnimation()
    }

    func stopAnimation(sender: AnyObject?)
    {
        //FLog(" called")

        // animate to stopped state
        isFadingOut = true
    }

    /// Only the spinning style is implemented
    func setStyle(style: NSProgressIndicatorStyle)
    {
        if (NSProgressIndicatorStyle.SpinningStyle != style) {
            assert(false, "Non-spinning styles not available.")
        }
    }

    func setColorX(value: NSColor)
    {
       // FLog("called")

            //FLog("set foreColor \(value)")
            foreColor = value

            // generate all the fin colors, with the alpha components
            // they already have
            for var i=0; i<numFins; i++ {
                //FLog("set finColors")
                let alpha: CGFloat = finColors[i].alphaComponent
                finColors.append(foreColor.colorWithAlphaComponent(alpha))

            }

            self.needsDisplay = true

    }

    func setBackgroundColor(value: NSColor)
    {
        if backColor != value {

            backColor = value;
            self.needsDisplay = true
        }
    }

    func setDrawsBackground(value: Bool)
    {
        if drawsBackground != value {
            drawsBackground = value
        }
        self.needsDisplay = true
    }

    func setIndeterminate(isIndeterminate: Bool)
    {
        indeterminate = isIndeterminate;
        if !indeterminate && isAnimating {

            self.stopAnimation(self)
        }
        self.needsDisplay = true
    }

    func setDoubleValue(doubleValue: Double)
    {
        // Automatically put it into determinate mode if it's not already.
        if indeterminate {
            self.indeterminate = false
        }
        currentValue = doubleValue;
        self.needsDisplay = true
    }

    func setMaxValue(maxValue: Double)
    {
        self.maxValue = maxValue;
        self.needsDisplay = true
    }

    func setUsesThreadedAnimation(useThreaded: Bool)
    {
        if (self.usesThreadedAnimation != useThreaded) {
            self.usesThreadedAnimation = useThreaded;

            if (isAnimating) {
                // restart the timer to use the new mode
                self.stopAnimation(self)
                self.startAnimation(self)
            }
        }
    }

    func setDisplayedWhenStopped(displayedWhenStopped: Bool)
    {
        self.displayedWhenStopped = displayedWhenStopped;

        // Show/hide ourself if necessary
        if (!isAnimating) {
            if displayedWhenStopped && self.hidden {
                self.hidden = false
            }
            else if !displayedWhenStopped && !self.hidden {
                self.hidden = true
            }
        }
    }

    func updateFrame(sender: AnyObject?)
    {
        //FLog(" called \(position)")

        if self.position > 0 {
            self.position--
        }
        else {
            self.position = self.numFins - 1
        }

        // update the colors
        let minAlpha:CGFloat = self.displayedWhenStopped ? kAlphaWhenStopped : 0.01;

        for var i=0; i<numFins; i++ {

            // want each fin to fade exponentially over _numFins frames of animation
            var newAlpha: CGFloat = self.finColors[i].alphaComponent * kFadeMultiplier
            if newAlpha < minAlpha {
                newAlpha = minAlpha
            }
            self.finColors[i] = self.foreColor.colorWithAlphaComponent(newAlpha)
            //FLog(" finColor[\(i)] = ?")
        }

        if self.isFadingOut {
            //FLog(" isFadingOut ")
            // check if the fadeout is done
            var done = true
            for var i=0; i<self.numFins; i++ {
                //FLog(" fabs = \(fabs(self.finColors[i].alphaComponent - minAlpha))")
                if fabs(self.finColors[i].alphaComponent - minAlpha) > 0.01 {
                    done = false
                    break
                }

            }
            if done {
                self.actuallyStopAnimation()
            }
        }
        else {
            //FLog(" light up ")
            // "light up" the next fin (with full alpha)
            self.finColors[position] = self.foreColor
        }

        if self.usesThreadedAnimation {
            // draw now instead of waiting for setNeedsDisplay (that's the whole reason
            // we're animating from background thread)
            self.display()
        }
        else {
            self.needsDisplay = true
        }
    }


    func actuallyStartAnimation()
    {
        //FLog(" called")

        // Just to be safe kill any existing timer.
        self.actuallyStopAnimation()

        isAnimating = true
        isFadingOut = false

        // always start from the top
        position = 1

        if !self.displayedWhenStopped {
            self.hidden = false
        }

        if self.window != nil {
            FLog(" self.window != nil")
            // Why animate if not visible?  viewDidMoveToWindow will re-call this method when needed.
            if self.usesThreadedAnimation {
                animationThread = NSThread(target: self, selector: "animateInBackgroundThread", object: nil)
                animationThread?.start()
            }
            else {
                animationTimer = NSTimer(timeInterval: NSTimeInterval(0.05), target: self, selector: "updateFrame:", userInfo: nil, repeats: true)


                NSRunLoop.currentRunLoop().addTimer(self.animationTimer!, forMode:NSRunLoopCommonModes)
                NSRunLoop.currentRunLoop().addTimer(self.animationTimer!, forMode:NSDefaultRunLoopMode)
                NSRunLoop.currentRunLoop().addTimer(self.animationTimer!, forMode:NSEventTrackingRunLoopMode)
            }
        } else {
           //FLog(" self.window == nil")
    }
    }


    func actuallyStopAnimation() {
        //FLog(" called")

        isAnimating = false
        isFadingOut = false

        if !self.displayedWhenStopped {
            self.hidden = true
        }
        if self.animationThread != nil {
            // we were using threaded animation
            self.animationThread!.cancel()
            if !self.animationThread!.finished {
                NSRunLoop.currentRunLoop().runMode(NSModalPanelRunLoopMode, beforeDate:NSDate(timeIntervalSinceNow: 0.05))
            }

            self.animationThread = nil
        }
        else if self.animationTimer != nil {
            // we were using timer-based animation
            self.animationTimer!.invalidate()

            self.animationTimer = nil
        }
        self.needsDisplay = true
    }

    func generateFinColorsStartAtPosition(startPosition: Int)
    {
        for var i=0; i<self.numFins; i++ {
            let oldColor: NSColor = self.finColors[i]
            let alpha = oldColor.alphaComponent
            self.finColors[i] = self.foreColor.colorWithAlphaComponent(alpha)
        }
    }

    func animateInBackgroundThread()
    {
        //FLog(" called")
        // Set up the animation speed to subtly change with size > 32.
        // int animationDelay = 38000 + (2000 * ([self bounds].size.height / 32));

        // Set the rev per minute here
        let omega: Int = 100 // RPM
        let animationDelay = 60 * 1000000 / omega / numFins
        var poolFlushCounter: Int = 0

        do {
            //FLog(" this is called")
            updateFrame(nil)

            usleep(useconds_t(animationDelay))
            poolFlushCounter++;
            if poolFlushCounter > 256 {

                poolFlushCounter = 0
            }
        } while (!NSThread.currentThread().cancelled)
        //FLog(" animateInBackgroundThread finished !")
    }

    override func drawRect(dirtyRect: NSRect) {
        //FLog(" called")
        let size: NSSize = self.bounds.size
        //FLog(" size \(size)")
        var theMaxSize: CGFloat = 0.0

        // Set the size to the minimum dimension
        if (size.width >= size.height) {
            theMaxSize = size.height
        } else {
            theMaxSize = size.width
        }

        if self.drawsBackground {
            backColor.set()

            NSBezierPath(rect: self.bounds).fill()
        }

        if let currentContext = NSGraphicsContext.currentContext() {

            var context = NSGraphicsContext.currentContext()!.CGContext


            NSGraphicsContext.saveGraphicsState()

            // Move the CTM so 0,0 is at the center of our bounds
            let w2 = self.bounds.size.width / 2.0
            let h2 = self.bounds.size.height / 2.0
            CGContextTranslateCTM(context,w2,h2)

            if (indeterminate) {
                //FLog(" indeterminate")
                var path = NSBezierPath()

                let lineWidth: CGFloat = 0.0859375 * theMaxSize; // should be 2.75 for 32x32
                let lineStart: CGFloat = 0.234375 * theMaxSize; // should be 7.5 for 32x32
                let lineEnd: CGFloat = 0.421875 * theMaxSize;  // should be 13.5 for 32x32

                path.lineWidth = lineWidth
                path.lineCapStyle = NSLineCapStyle.RoundLineCapStyle
                path.moveToPoint(NSMakePoint(0, lineStart))
                path.lineToPoint(NSMakePoint(0, lineEnd))

                for var i = 0; i<numFins; i++ {
                    if isAnimating {
                        finColors[i].set()
                    } else {
                        foreColor.colorWithAlphaComponent(kAlphaWhenStopped).set()
                    }

                    path.stroke()

                    // we draw all the fins by rotating the CTM, then just redraw the same segment again
                    let r: CGFloat = 6.282185 / CGFloat(numFins)
                    CGContextRotateCTM(context, r)
                }

            } else {
                //FLog(" !indeterminate")
                let lineWidth:CGFloat = 1 + (0.01 * theMaxSize)
                let circleRadius:CGFloat  = (theMaxSize - lineWidth) / 2.1
                let circleCenter:NSPoint  = NSMakePoint(0, 0)
                foreColor.set()
                var path = NSBezierPath()
                path.lineWidth = lineWidth
                path.appendBezierPathWithOvalInRect(NSMakeRect(-circleRadius, -circleRadius, circleRadius*2, circleRadius*2))
                path.stroke()

                path = NSBezierPath()

                let endAngle = 90.0 - (360.0 * currentValue / maxValue)

                path.appendBezierPathWithArcWithCenter(circleCenter, radius: circleRadius, startAngle: 90.0, endAngle: CGFloat(endAngle), clockwise:true)


                path.lineToPoint(circleCenter)
                path.fill()

            }
            NSGraphicsContext.restoreGraphicsState()
        }
    }

}

CoreAnimation警告:

CoreAnimation: warning, deleted thread with uncommitted CATransaction; created by:
0   QuartzCore                          0x00007fff845180ea _ZN2CA11Transaction4pushEv + 312
1   QuartzCore                          0x00007fff84517f8a _ZN2CA11Transaction15ensure_implicitEv + 276
2   QuartzCore                          0x00007fff8451d313 _ZN2CA5Layer13thread_flags_EPNS_11TransactionE + 37
3   QuartzCore                          0x00007fff84526941 _ZN2CA5Layer13needs_displayEv + 45
4   QuartzCore                          0x00007fff8452690c -[CALayer needsDisplay] + 21
5   AppKit                              0x00007fff853576a3 -[NSView viewWillDraw] + 1078
6   AppKit                              0x00007fff85356340 -[NSView _sendViewWillDrawInRect:clipRootView:] + 1417
7   AppKit                              0x00007fff85337de6 -[NSView displayIfNeeded] + 1216
8   Sample                              0x000000010004d6fd _TFC20SISU_Sample7Spinner11updateFramefS0_FGSqPSs9AnyObject__T_ + 2717
9   Sample                              0x000000010004f78b _TFC20SISU_Sample7Spinner25animateInBackgroundThreadfS0_FT_T_ + 219
10  Sample                              0x000000010004f8c2 _TToFC20SISU_Sample7Spinner25animateInBackgroundThreadfS0_FT_T_ + 34
11  Foundation                          0x00007fff91b02b7a __NSThread__main__ + 1345
12  libsystem_pthread.dylib             0x00007fff87f5b2fc _pthread_body + 131
13  libsystem_pthread.dylib             0x00007fff87f5b279 _pthread_body + 0
14  libsystem_pthread.dylib             0x00007fff87f594b1 thread_start + 13

1 个答案:

答案 0 :(得分:1)

看起来您正在后台线程中更改NSView的状态。 你不能这样做。

如果你调用lockFocusIfCanDraw,你可以能够绘制视图,虽然最后我总是放弃这些尝试......你不能安全地改变状态

if ( [view lockFocusIfCanDraw] )
{
    [view drawRect:[view bounds]];
    [view unlockFocus];
}

如果是我,我会放弃这种方法......你的主线程应该只用于UI交互&amp;图。

相反,任何花费超过0.2秒的任务都应该在后台线程中完成,但绘制不应该在后台线程中完成。 不幸的是,这说起来容易做起来难。