The frame is getting updated in 0.2 seconds or so, rather than the desired amount (0.5). When I try 5 seconds it still goes the same speed of around 0.2 seconds.
What am I doing wrong?
Here is my entire method:
#IBAction func photoFullscreenButtonReleased(sender: AnyObject) {
photoFullscreen = !photoFullscreen
if photoFullscreen {
contentHeightConstraint.constant = self.view.frame.height
animateUpdateConstraints()
UIView.animateWithDuration(5) {
self.previewLayer!.frame = self.cameraView.bounds
}
}
else {
contentHeightConstraint.constant = contentHeight
animateUpdateConstraints()
UIHelper.delay(0.25, closure: { () -> () in
UIView.animateWithDuration(0.5) {
self.previewLayer!.frame = self.cameraView.bounds
}
})
}
}
Potential function that could be causing problems?
func animateUpdateConstraints() {
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
Related
swift version is 5 and lottie version is 3.1.1
I want to show two animations Json file with Lottie, that way fade in first animation and after it done it fades out and the another one fades in and I have to take a loop and do it on an infinity loop.
boardAnimationViewForSecondSlide = AnimationView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.width * (690 / 750)))
boardAnimationViewForSecondSlide?.animation = Animation.named("Slidetwop1")
slide.addSubview(boardAnimationViewForSecondSlide)
and I define a closure for handle completion play's method
private var animationState: Int = 0 // 0 first slid, 1 second slide
private var changeStateInSlide2: (Bool) -> Void = { finish in
if animationState == 0 {
boardAnimationViewForSecondSlide.animation = Animation.named("Slidetwop2.json")
playSecondPage = true
} else {
boardAnimationViewForSecondSlide.animation = Animation.named("Slidetwop1.json")
playSecondPage = true
}
}
fileprivate var playSecondPage: Bool {
get {
return false
}
set {
if newValue {
boardAnimationViewForSecondSlide.play(completion:changeStateInSlide2)
}
}
}
I think the moste simple is to create a func to launch the animation.
example :
/// Start animation with Lottie
func startAnimation(viewName: AnimationView, jsonName: String) {
viewName.isHidden = false
viewName.animation = Animation.named(jsonName)
viewName.play { (_) in
viewName.isHidden = true
}
After this you can simply call the method one after one :
startAnimation(viewName: checkAnimation, jsonName: "Slidetwop1")
startAnimation(viewName: checkAnimation, jsonName: "Slidetwop2")
Or use the completion handler to call the second.
EDIT: For the loop you can use this solution:
/// Start animation with Lottie
func startAnimation() {
animationLottieView.animation = Animation.named("Slidetwop1")
animationLottieView.play { (finished) in
// completion handler
self.animationLottieView.animation = Animation.named("Slidetwop2")
self.animationLottieView.play { (finishedAnimation) in
self.startAnimation()}
}
}
I have a super simple UIView animation where the origin y value fails to animate to 88 on the first try if i change the input text set in the UILabels.
The animation runs fine on the 2nd attempt. It feels like an initialization problem. Running layoutSubViews and updateConstraints is not helping. Thanks for any tips on this.
func previewDisplay(notifView: UIView, hdrView: UIView) {
populateText()
self.notifView?.frame.origin.y = 0
self.notifView?.frame.size.height = 33
self.notifView?.layoutSubviews()
self.notifView?.updateConstraints()
self.notifView = notifView
self.closeBtn.isHidden = true
self.notifBodyLabel.isHidden = true
self.closeBtn.alpha = 0
self.notifBodyLabel.alpha = 0
UIView.animate(withDuration: 1.0, animations: {
self.notifView?.frame.origin.y = 88
}, completion: nil)
}
func populateText() {
if let info = notification?.userInfo as? Dictionary<String,String> {
// Check if value present before using it
if let t = info["title"] {
self.notifTitleMessageLabel.text = t
} else {
self.notifTitleMessageLabel.text = ""
}
if let b = info["body"] {
self.notifBodyLabel.text = b
} else {
self.notifBodyLabel.text = ""
}
}
}
I have a for loop as follows:
#objc private func resetValue() {
for i in stride(from: value, to: origValue, by: (value > origValue) ? -1 : 1) {
value = i
}
value = origValue
}
And when value is set it updates a label:
private var value = 1 {
didSet {
updateLabelText()
}
}
private func updateLabelText() {
guard let text = label.text else { return }
if let oldValue = Int(text) { // is of type int?
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
} else {
label.text = "\(value)"
}
}
I was hoping that if value=5 and origValue=2, then the label would flip through the numbers 5,4,3,2. However, this is not happening - any suggestions why, please?
I've tried using a delay function:
func delay(_ delay:Double, closure: #escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
and then placing the following within the stride code:
delay(2.0) { self.value = i }
However, this doesn't seem to work either.
Thanks for any help offered.
UIKit won't be able to update the label until your code is finished with the main thread, after the loop completes. Even if UIKit could update the label after each iteration of the loop, the loop is going to complete in a fraction of a second.
The result is that you only see the final value.
When you attempted to introduce the delay, you dispatched the update to the label asynchronously after 0.5 second; Because it is asynchronous, the loop doesn't wait for the 0.5 second before it continues with the next iteration. This means that all of the delayed updates will execute after 0.5 seconds but immediately one after the other, not 0.5 seconds apart. Again, the result is you only see the final value as the other values are set too briefly to be visible.
You can achieve what you want using a Timer:
func count(fromValue: Int, toValue: Int) {
let stride = fromValue > toValue ? -1 : 1
self.value = fromValue
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats:true) { [weak self] (timer) in
guard let strongSelf = self else {
return
}
strongSelf.value += stride
if strongSelf.value == toValue {
timer.invalidate()
}
}
}
I would also update the didSet to send the oldValue to your updateLabelText rather than having to try and parse the current text.
private var value = 1 {
didSet {
updateLabelText(oldValue: oldValue, value: value)
}
}
private func updateLabelText(oldValue: Int, value: Int) {
guard oldValue != value else {
self.label.text = "\(value)"
return
}
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
}
I would like to be able to only allow the player to shoot a missile every 0.7 seconds. How do I do this? Here is my code. I have tried other methods I found on this site but they do not work.
func fireTorpedo() {
if !self.player.isPaused
{
if isSoundEffect == true {
self.run(SKAction.playSoundFileNamed("Rocket", waitForCompletion: false))
}
let torpedoNode = SKSpriteNode(imageNamed: "Rocket")
torpedoNode.position = player.position
torpedoNode.position.y += 5
torpedoNode.physicsBody = SKPhysicsBody(circleOfRadius: torpedoNode.size.width / 2)
torpedoNode.physicsBody?.isDynamic = true
torpedoNode.physicsBody?.categoryBitMask = photonTorpedoCategory
torpedoNode.physicsBody?.contactTestBitMask = alienCategory
torpedoNode.physicsBody?.collisionBitMask = 0
torpedoNode.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(torpedoNode)
let animationDuration:TimeInterval = 0.5
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: player.position.x, y: self.frame.size.height + 10), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
torpedoNode.run(SKAction.sequence(actionArray))
}
}
I like to do that like this:
class GameScene:SKScene {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if action(forKey: "shooting") == nil {
let wait = SKAction.wait(forDuration: 0.7)
run(wait, withKey: "shooting")
print("shot fired")
}
}
}
While there is a key "shooting" found on a scene, you can't shoot anymore. In real game, this would be likely the part of a node (an action should be run on a node).
First, add a flag at class-level that indicates whether the player can fire a torpedo.
var canFireTorpedo = true
Then, at the end of the fireTorpedo method, set canFireTorpedo to false, then set it to true again after 0.7 seconds:
canFireTorpedo = false
player.run(SKAction.sequence([
SKAction.wait(forDuration: 0.7),
SKAction.run { [weak self] in self?.canFireTorpedo = true }]))
After that, check canFireTorpedo at the start of the method:
func fireTorpedo() {
if canFireTorpedo {
// put everything in the method here, including the parts I added
}
}
Hopefully this code is self-explanatory.
To launch every 0.7 seconds you can do:
let actionKey = "repeatLaunchMethod"
let launchMethod = SKAction.afterDelay(0.7, runBlock: {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.fireTorpedo()
})
// do this if you want to repeat this action forever
self.player.run(SKAction.repeatForever(launchMethod), withKey: actionKey)
// do this if you want to repeat this action only n times
// self.player.run(SKAction.repeat(launchMethod, count: 5), withKey: actionKey)
To stop this action you can do:
if self.player.action(forKey: actionKey) != nil {
self.player.removeAction(forKey: actionKey)
}
Extension used to render the code more readable:
extension SKAction {
/**
* Performs an action after the specified delay.
*/
class func afterDelay(_ delay: TimeInterval, performAction action: SKAction) -> SKAction {
return SKAction.sequence([SKAction.wait(forDuration: delay), action])
}
/**
* Performs a block after the specified delay.
*/
class func afterDelay(_ delay: TimeInterval, runBlock block: #escaping () -> Void) -> SKAction {
return SKAction.afterDelay(delay, performAction: SKAction.run(block))
}
}
I've got a Collection View and have arranged it in such a way that there is a single column on one side of the screen and content that is replaced based on what collection item is in focus.
I'd like to be able to on swap the content out if focus on an item has been held for more then .5 seconds.
Here is what I currently have, and it swaps the data out instantly.
if self.focused {
self.label.alpha = 1
self.priceLabel.alpha = 1
if self.representedDataItem?.imageUrl! == "https://s3-us-west-2.amazonaws.com/random/image.png" ||
self.representedDataItem?.imageUrl! == "" {
self.backgroundImage.image = UIImage(named: "titleImage")
}
else {
ImageCache.sharedLoader.imageForUrl((self.representedDataItem?.imageUrl!)!, completionHandler:{(image: UIImage?, url: String) in
self.backgroundImage.image = image!
})
}
}
else {
self.label.alpha = 0.2
self.priceLabel.alpha = 0.2
}
Don't know the self.focused how to determine, assume you already took care of it. Therefore, you could use GCD to delay the execution while focused.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
// code to be executed after 0.5 sec
}
if self.focused {
self.label.alpha = 1
self.priceLabel.alpha = 1
}
else {
self.label.alpha = 0.2
self.priceLabel.alpha = 0.2
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
// code to be executed after 0.5 sec
if self.focused {
if self.representedDataItem?.imageUrl! == "https://s3-us-west-2.amazonaws.com/random/image.png" ||
self.representedDataItem?.imageUrl! == "" {
self.backgroundImage.image = UIImage(named: "titleImage")
}
else {
ImageCache.sharedLoader.imageForUrl((self.representedDataItem?.imageUrl!)!, completionHandler:{(image: UIImage?, url: String) in
self.backgroundImage.image = image!
})
}
}
}