material-motion-archive / runtime-objc

Archived February 16, 2017 :: Material Motion Runtime for Apple devices
Apache License 2.0
20 stars 3 forks source link

Provide support for completion callbacks on subsets of plans #125

Open jverkoey opened 7 years ago

jverkoey commented 7 years ago

Idea 1 for API

Blocked-based

Pros:

  1. Obvious which plans are part of the activity group.

Cons:

  1. Requires an extra layer of scope (not really a big deal).
runtime.activityGroup {
  runtime.addPlan(tween, to: layer)
}.onStateChange { state in
  if state == .idle {
    // Do something.
  }
}
jverkoey commented 7 years ago

Idea 2 for API

Method-based

Pros:

  1. Easy to use for small numbers of plans.
  2. Doesn't introduce layer of scope like Idea 1.

Cons:

  1. Difficult to generalize.
  2. Requires building all plans and then adding them as a unit.
class Runtime {
  func addPlan(_ plan: Plan, to target: AnyObject, onIdle: () -> Void)
}
runtime.addPlan(tween, to: layer) {
  // Invoked on idle
}
runtime.addPlans([tween], to: layer) {
  // Invoked on idle
}
jverkoey commented 7 years ago

For Idea 2 I could argue for the callback being called on any state change rather than just on idle.

jverkoey commented 7 years ago

Idea 3 for API

CATransaction-esque

Pros:

  1. Doesn't introduce layer of scope like Idea 1.

Cons:

  1. Too easy to forget to end an activity group.
let group = runtime.beginActivityGroup()
group.onActiveStateChange = {
  // Do something
}
runtime.addPlan(...)
group.endActivityGroup()
jverkoey commented 7 years ago

Rough plan of work ahead:

  1. Modify token generator API to require a plan object.
  2. Add activate and deactivate APIs to the token, allowing their reuse.
  3. Update downstream APIs with new token generation APIs.
  4. Build new activity group API to verify that the per-plan tokens enable this to be built.
  5. Cut releases.
jverkoey commented 7 years ago

Example token generator API:

let token = tokenGenerator.generate(forPlan: plan)
token.activate()
token.deactivate()
jverkoey commented 7 years ago

Core animation implementation:

guard let token = tokenGenerator.generate(forPlan: tween) else { return }
token.activate()

CATransaction.begin()

CATransaction.setCompletionBlock {
  token.deactivate()
}

target.add(animation, forKey: nil)

CATransaction.commit()
jverkoey commented 7 years ago

Gesture implementation:

var gestureTokens: [UIGestureRecognizer: [IsActiveTokenable]] = [:]
func addPlan(_ plan: Plan) {
  let plan = plan as! Gesturable

  ....

  gestureTokens[gestureRecognizer].append(tokenGenerator.generate(forPlan: plan))
}

func handle(gesture: UIGestureRecognizer) {
  if gesture.state == .began && gestureTokens[gesture] == nil {
    gestureTokens[gesture].forEach { $0.activate() }

  } else if gesture.state == .ended || gesture.state == .cancelled {
    gestureTokens[gesture].forEach { $0.deactivate() }
  }
}