2

Swift 5, iOS 13

I am running this code, it works.

var body: some View {
...
Button(action: {
  self.animateTLeft() 
  quest = quest + "1"
}) { Wedge(startAngle: .init(degrees: 180), endAngle: .init(degrees: 270)) 
         .fill(Color.red) 
         .frame(width: 200, height: 200)
         .offset(x: 95, y: 95)
         .scaleEffect(self.tLeft ? 1.1 : 1.0)
}.onReceive(rPublisher) { _ in
  self.animateTLeft() 
}
...
}

private func animateTLeft() {
withAnimation(.linear(duration: 0.25)){
  self.tLeft.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
  withAnimation(.linear(duration: 0.25)){
    self.tLeft.toggle()
  }
})
}

And I want to try some alternative approaches not using GCD if possible. So I tried using a Timer, which didn't compile. And I tried using perform, which also didn't compile. So I tried operations which compiled! :). But sadly then only works once. Is there no alternative to GCD.

private func animateTLeft() {

//perform(#selector(animate), with: nil, afterDelay: 0.25)
//Timer.scheduledTimer(timeInterval: 0.15, target: self, selector: #selector(animateRed), userInfo: nil, repeats: false)

let queue = OperationQueue()
let operation1 = BlockOperation(block: {
  withAnimation(.linear(duration: 1)){
    self.tLeft.toggle()
  }
})
let operation2 = BlockOperation(block: {
  withAnimation(.linear(duration: 1)){
    self.tLeft.toggle()
  }
})
operation2.addDependency(operation1)
queue.addOperations([operation1,operation2], waitUntilFinished: true)

}

Why do I want to do this, cause I have four slices that I want to animate, but and I want less code. I have red, green, yellow and blue slice. I wrote a generic routine to animate them, supplying the color as a parameter. This is my code.

private func animateSlice(slice: inout Bool) {
withAnimation(.linear(duration: 0.25)){
  slice.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
  withAnimation(.linear(duration: 0.25)){
    slice.toggle()
  }
})
}

But this won't compile with the inout parameter giving an a red error message "Escaping closure captures 'inout' parameter 'slice'", which isn't good. I can make it a copy, not in inout parameter. That compiles, but of course doesn't work, cause it isn't changing the stupid value.

Also tried this, but it won't compile. Maybe someone out there can make it work.

private func animateSliceX(slice: String) {
var ptr = UnsafeMutablePointer<Bool>.allocate(capacity: 1)
switch slice {
case "red": ptr = &tLeft
case "green": ptr = &tRight
case "yellow": ptr = &bLeft
default: ptr = &bRight
}
withAnimation(.linear(duration: 0.25)){
  ptr.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
  withAnimation(.linear(duration: 0.25)){
    ptr.toggle()
  }
})
}

Thanks...

1

3 Answers 3

2

If I were to use GCD, I might use Combine methods, e.g.:

struct ContentView: View {
    var body: some View {
        Button(action: {
            DispatchQueue.main.schedule(after: .init(.now() + 1)) {
                print("bar")
            }
        }) {
            Text("foo")
        }
    }
}

If you don’t want to use GCD, you can use a Timer:

struct ContentView: View {
    var body: some View {
        Button(action: {
            Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
                print("bar")
            }
        }) {
            Text("foo")
        }
    }
}

Or as user3441734 said, you can use schedule on RunLoop, too:

struct ContentView: View {
    var body: some View {
        Button(action: {
            RunLoop.main.schedule(after: .init(Date() + 1)) {
                print("bar")
            }
        }) {
            Text("foo")
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

I don't think there's another way (at least easier to use that GCD), but you could use the following reusable function.

Supposing that slice is a State or a Binding with a value type of Bool you could do this:

private func animateSlice(slice: Binding<Bool>) {
    DispatchQueue.main.asyncAfter(wallDeadline: .now() + 0.25) {
        withAnimation(Animation.linear(duration: 0.25)) {
            slice.wrappedValue.toggle()
        }
    }
}

With the function above compiles because Swift doesn't consider changing a Binding wrapped value a mutation. As such, you can pass it to your asyncAfter block.

To use the function you need a binding:

self.animateSlice(slice: $self.tLeft)

1 Comment

I did come up with another version [that works], but it is 12 lines of code and I you did in 8. Which means you win :) Excellent coding.. Will be publishing my code in Better programming on medium.com. Add an email to your profile Filipe so I can give you some credit.
0

OK, so I accepted the other answer, cause I thought it was a better one than this one, but thought I would post this anyway, cause it another solution to the same problem.

private func animate(slice: String) {
    animateAction(slice: slice)
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
      self.animateAction(slice: slice)
    })
  }

 private func animateAction(slice: String) {
    withAnimation(.linear(duration: 0.25)){
      switch slice {
        case "red": self.tLeft.toggle()
        case "green": self.tRight.toggle()
        case "yellow": self.bLeft.toggle()
    // blue is the only other slice
        default: self.bRight.toggle()
      }
    }
  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.