Альтернативы GCD для запуска кода с задержкой под SwiftUI - PullRequest
1 голос
/ 25 марта 2020

Swift 5, iOS 13

Я запускаю этот код, он работает.

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()
  }
})
}

И я хочу попробовать некоторые альтернативные подходы не , используя GCD если возможно. Поэтому я попытался использовать таймер, который не компилируется. И я попытался использовать перформанс, который также не компилировался. Так что я попробовал операции, которые скомпилированы! :). Но, к сожалению, тогда работает только один раз. Нет ли альтернативы 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)

}

Почему я хочу это сделать, потому что у меня есть четыре фрагмента, которые я хочу анимировать, но я хочу меньше кода. У меня есть красный, зеленый, желтый и синий ломтик. Я написал стандартную подпрограмму c для их анимации, предоставив цвет в качестве параметра. Это мой код.

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()
  }
})
}

Но это не скомпилируется с параметром inout, который выдает красное сообщение об ошибке «Escapeing closure захватывает параметр inout« slice », что не очень хорошо. Я могу сделать копию, а не в параметре inout. Это компилируется, но, конечно, не работает, потому что это не меняет глупое значение.

Также попытался, но это не скомпилируется. Может быть, кто-то может заставить это работать.

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()
  }
})
}

Спасибо ...

Ответы [ 3 ]

1 голос
/ 25 марта 2020

Я не думаю, что есть другой способ (по крайней мере, проще использовать этот GCD), но вы могли бы использовать следующую многократно используемую функцию.

Предположим, что срез является State или Binding с типом значения В общем, вы могли бы сделать это:

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

С помощью функции выше компилируется, потому что Swift не считает изменение обернутого значения Binding мутацией. Таким образом, вы можете передать его в свой блок asyncAfter.

Чтобы использовать функцию, вам нужна привязка:

self.animateSlice(slice: $self.tLeft)
0 голосов
/ 16 апреля 2020

Если бы я использовал GCD, я мог бы использовать методы Combine, например:

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

Если вы не хотите использовать GCD, вы можете использовать Timer:

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

Или, как сказал пользователь 3441734, вы также можете использовать schedule на RunLoop:

struct ContentView: View {
    var body: some View {
        Button(action: {
            RunLoop.main.schedule(after: .init(Date() + 1)) {
                print("bar")
            }
        }) {
            Text("foo")
        }
    }
}
0 голосов
/ 26 марта 2020

ОК, поэтому я принял другой ответ, потому что я думал, что он лучше, чем этот, но думал, что я все равно опубликую это, потому что это другое решение той же проблемы.

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()
      }
    }
  }
...