2014-12-18 3 views
1

Я создал NSProgressIndicator Spinner, используя пользовательский NSView, как показано ниже, но получив ошибку Uncommitted CATransaction, показанную ниже.Как исправить предупреждение незаблокированного 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

Похоже, вы меняете состояние вашего NSView в фоновом потоке. Вы не можете этого сделать.

Вы может быть в состоянии сделать вид, если вы звоните lockFocusIfCanDraw, хотя в конце концов, я всегда отказался от этих попыток ... вы не можете изменить состояние безопасно.

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

Если бы это было, я бы отказаться от такого подхода ... ваш основной поток должен использоваться только для взаимодействия UI & чертежа.

Вместо этого любая задача, которая занимает более 0,2 секунды, должна выполняться в фоновом потоке, но рисование не должно выполняться в фоновом потоке. К сожалению, это также легче сказать, чем сделать.

+0

Ммм, ну, я на самом деле не вызываю drawRect из фонового потока, я называю self.display() 'или' self.needsDisplay = true '. Последний работает отлично, но «self.display()» дает эту ошибку. Я предполагаю, что это вызвано тем, что вызов self.display() приводит к тому, что 'drawRect()' вызывается из того же потока, что и 'self.display()'. Итак, есть ли какая-либо польза в отправке «self.display()» в основной поток (с ожиданием), просто устанавливая «self.needsDisplay = true»? –

Смежные вопросы