2012-05-30 24 views
10

我試圖在遠程通知到達時在屏幕上顯示自定義UIView 5秒鐘。如何用GCD做'序列'動畫?

這樣的代碼:

//customView.alpha = 1.0 here 
[UIView animateWithDuration:1 animations:^{ 
            customView.alpha = 0.3; 
           } 
           completion:^(BOOL finished){ 
            // remove customView from super view. 
           }]; 

的問題,我需要什麼樣的

但這兒有情況,一對夫婦的通知,可能會在很短的時間間隔,在這幾個customView可能同時進行動畫製作,並且可以覆蓋其他人。

我希望這些動畫能夠一個接一個地執行,這樣它們就不會發生衝突。

假設,但未能

//(dispatch_queue_t)queue was created in other parts of the code 
dispatch_sync(queue, ^{ 
    [UIView animationWithDuration:animations:...]; 
}); 

使得在GCD隊列中的動畫後,我得到了相同的結果,爲我所用的起源代碼,沒有使用GCD。動畫仍然是相互衝突的。

BTW,聽說動畫或任務涉及UI應該總是在主線程中運行,但在我的第二個代碼中的動畫似乎做smoothy。爲什麼?

回答

4

如果每次都運行相同的動畫,則可以存儲動畫應該運行的次數(與動畫的repeat count屬性不同)。

當您收到遠程通知時,您將遞增計數器並調用計數器恰好爲1時的動畫方法。然後在methodThatAnimates中遞歸地調用自己在完成塊中,同時每次減少計數器。它看起來像這樣(與僞的方法名):

- (void)methodThatIsRunWhenTheNotificationIsReceived { 
    // Do other stuff here I assume... 
    self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation + 1; 
    if ([self.numberOfTimesToRunAnimation == 1]) { 
     [self methodThatAnimates]; 
    } 
} 

- (void)methodThatAnimates { 
    if (self.numberOfTimesToRunAnimation > 0) { 
     // Animation preparations ... 
     [UIView animateWithDuration:1 
         animations:^{ 
            customView.alpha = 0.3; 
         } 
         completion:^(BOOL finished){ 
            // Animation clean up ... 
            self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation - 1; 
            [self methodThatAnimates]; 
         }]; 
    } 
} 
+1

你有和@Ducan類似的想法。感謝您的代碼。你認爲我們應該鎖定'self.numberOfTimesToRunAnimation'嗎? – studyro

+0

是的。通過不將該屬性定義爲「非原子」並且從不直接訪問該變量(始終使用該屬性),系統將爲您鎖定該變量,以便兩個線程不會同時讀取/寫入該變量。 –

+0

非常好。我用它來控制segmentedControl UI。通過將'selectedIndex'放入屬性中,並在完成時將其設置爲'NSNotFound',我不必在動畫過程中禁用該控件。謝謝! –

0

我會建議在完成塊中發送消息給任何觸發動畫的對象。然後,您可以讓該對象排隊通知本身,並在每次收到該消息時啓動下一個通知。

1

你可以使用一個(非)併發NSOperationQueue「一步一步」

的NSOperationQueue類調節一組的NSOperation對象的執行來進行動畫。在添加到隊列後,操作將保留在該隊列中,直到它被明確取消或完成其任務。隊列中的操作(但尚未執行)本身根據優先級和操作間對象依賴性進行組織,並相應執行。應用程序可能會創建多個操作隊列並將操作提交給它們中的任何一個。

操作間依賴關係爲 操作提供絕對執行順序,即使這些操作位於不同的 操作隊列中。操作對象不被視爲準備好執行,直到其所有從屬操作完成執行。 對於準備好執行的操作,操作隊列總是 執行相對於其他準備好的 操作具有最高優先級的操作。

+1

animateWithDuration:......是異步的。這將如何解決問題?每個操作都會在動畫完成之前立即完成。 –

+0

在動畫的完成塊中設置isFinished屬性。從文檔:「已完成的關鍵路徑可以讓客戶知道某個操作已成功完成任務或被取消並正在退出。」 – CarlJ

4

使用隊列提交的序列動畫是行不通的,因爲這會立即開始動畫回報的方法,和動畫添加到動畫樹將在稍後執行。隊列中的每個條目都會在很短的時間內完成。

如果每個動畫上了同樣的觀點,然後運行在默認情況下,系統應該讓每一個動畫完成運行後它開始下一個之前。

引述文檔的UIViewAnimationOptionBeginFromCurrentState期權價值:

UIViewAnimationOptionBeginFromCurrentState

從 開始動畫與已飛行動畫製作相關的當前設置。 如果該鍵不存在,任何飛行動畫允許新的動畫開始之前完成。如果另一個動畫是在飛行 沒有這個鍵,沒有效果。

如果你想鏈上的一系列動畫製作,這裏就是我會做:

創建動畫塊的可變數組。 (代碼塊是對象,並且可以被添加到陣列)收件拉動頂部動畫塊關閉陣列(和從陣列中移除),並且使用animateWithDuration提交它的方法:動畫:完成後,將完成的方法簡單地,其中再次調用該方法。在從數組中拉出項目之前,使代碼聲明一個鎖定,並在刪除項目後釋放鎖定。

然後你就可以編寫出斷言動畫陣列鎖,添加動畫塊鎖,並釋放鎖響應來電通知代碼。

+0

謝謝!我認爲這是做我需要的正確方法。 – studyro

0

ProcedureKit(基於NSOperation)是現成的解決方案的一個例子,但它只是用於動畫的重量級。

Operation子類,我用它來排隊動畫彈出窗口和其他的東西:

class SerialAsyncOperation: Operation { 

    private var _started = false 

    private var _finished = false { 
     willSet { 
      guard _started, newValue != _finished else { 
       return 
      } 
      willChangeValue(forKey: "isFinished") 
     } 
     didSet { 
      guard _started, oldValue != _finished else { 
       return 
      } 
      didChangeValue(forKey: "isFinished") 
     } 
    } 

    private var _executing = false { 
     willSet { 
      guard newValue != _executing else { 
       return 
      } 
      willChangeValue(forKey: "isExecuting") 
     } 
     didSet { 
      guard oldValue != _executing else { 
       return 
      } 
      didChangeValue(forKey: "isExecuting") 
     } 
    } 

    override var isAsynchronous: Bool { 
     return true 
    } 

    override var isFinished: Bool { 
     return _finished 
    } 

    override var isExecuting: Bool { 
     return _executing 
    } 

    override func start() { 
     guard !isCancelled else { 
      return 
     } 
     _executing = true 
     _started = true 
     main() 
    } 

    func finish() { 
     _executing = false 
     _finished = true 
    } 

    override func cancel() { 
     _executing = false 
     _finished = true 
     super.cancel() 
    } 
} 

使用示例:

// Setup a serial queue 
private lazy var serialQueue: OperationQueue = { 
    let queue = OperationQueue() 
    queue.maxConcurrentOperationCount = 1 
    queue.name = String(describing: type(of: self)) 
    return queue 
}() 

// subclass SerialAsyncOperation 
private class MessageOperation: SerialAsyncOperation { 

    // ... 

    override func main() { 
     DispatchQueue.main.async { [weak self] in 
      // do UI stuff 

      self?.present(completion: { 
       self?.finish() 
      }) 
     } 
    } 

    func present(completion: @escaping() -> Void) { 
     // do async animated presentation, calling completion() in its completion 
    } 

    func dismiss(completion: @escaping() -> Void) { 
     // do async animated dismissal, calling completion() in its completion 
    } 

    // animated cancellation support 
    override func cancel() { 
     if isExecuting { 
      dismiss(completion: { 
       super.cancel() 
      }) 
     } else { 
      super.cancel() 
     } 
    } 
} 

基本上,只要添加此操作到串行隊列,記得在完成異步內容時調用finish()。您也可以用一次呼叫取消串行隊列上的所有操作,這些操作將優雅地解除。