UITableView beginUpdates endUpdates not working with auto layouts - ios

Here is my screen. Consider the first row till Repeat switch :
Following is the switch action :
/**
Function name : repeatAction
Repeat UISwitch action
**/
#IBAction func repeatAction(sender: UISwitch)
{
if sender.isOn
{
cellDateTime.constrain_viewRepeatHeight.constant = 80
UIView.transition(with: self.cellDateTime.view_repeat, duration: 0.6, options: .transitionCrossDissolve, animations: {() -> Void in
self.cellDateTime.view_repeat.isHidden = false
}, completion: { _ in })
}
else
{
UIView.transition(with: self.cellDateTime.view_repeat, duration: 0.6, options: .transitionCrossDissolve, animations: {() -> Void in
self.cellDateTime.view_repeat.isHidden = true
}, completion: { _ in
self.cellDateTime.constrain_viewRepeatHeight.constant = 0
})
}
let offset = self.table_InitialData.contentOffset
self.table_InitialData.layer.removeAllAnimations()
self.table_InitialData.beginUpdates()
self.table_InitialData.endUpdates()
self.table_InitialData.contentOffset = offset
}
When switch is in on state for the first time, it works fine :
Now when the switch is in off state again, ideally it should appear as in the first screenshot. However, it looks like this :
I am using auto layouts. Just to mention, previously I had the same controls in a simple controller view and that worked fine. But since I placed them in UITableView, it is getting weird.

Related

Trigger fading label delay from view did load instead of at the end of previous fade

Background
Thanks to the excellent answer here, I've managed to make a series of labels fade in sequentially.
However, the delay referred to in the function in the extension only triggers when the previous fade has completed (I think) so there is at least 1 second between them. Ideally I want to edit the model so that the delay refers to from the time the view loads, not the time the previous one has completed.
i.e. If I make the delay quite small, the second one would start fading in before the first one had finished.
This is the extension :
extension UIView {
func fadeIn(duration: TimeInterval = 1.0, delay: TimeInterval = 0.0, completion: ((Bool)->())? = nil) {
self.alpha = 0.0
UIView.animate(withDuration: duration, delay: delay, options: .curveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion)
}
}
and this is my implementation on a VC with 6 labels :
func animateLabels() {
self.titleOutlet.alpha = 0.0
self.line1Outlet.alpha = 0.0
self.line2Outlet.alpha = 0.0
self.line3Outlet.alpha = 0.0
self.line4Outlet.alpha = 0.0
self.line5Outlet.alpha = 0.0
self.line6Outlet.alpha = 0.0
self.titleOutlet.fadeIn(delay: 0.2, completion: { _ in
self.line1Outlet.fadeIn(delay: 0.4, completion: { _ in
self.line2Outlet.fadeIn(delay: 0.6, completion: { _ in
self.line3Outlet.fadeIn(delay: 0.8, completion: { _ in
self.line4Outlet.fadeIn(delay: 1.0, completion: { _ in
self.line5Outlet.fadeIn(delay: 1.2, completion: { _ in
self.line6Outlet.fadeIn(delay: 1.4, completion: { _ in
})})})})})})})
}
What am I trying to get to?
So what I'm trying to get to is having line2outlet start to fade 0.4 seconds after the load of the page but it is starting only when line1 has finished fading in (which takes 1 second and is delayed by 0.2s).
What have I tried?
I have attempted to write my own delay function after reading up. I tried adding this to the extension :
func delay(interval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
closure()
}
}
and implementing like this :
self.line1Outlet.fadeIn(delay: 0, completion: { _ in
delay(interval: 1, closure: ()->Void)
but that got loads of errors! I also tried setting minus numbers for the delays but that didn't work either.
Any help much appreciated!
One possible solution that I have used before is to use a loop with an incrementing delay variable feeding into the standard UIView.animate() method. This method does not use completion handlers.
Make an array of the labels:
var labels = [titleOutlet, line1Outlet] // etc...
Use this extension:
extension UIView {
func fadeIn(duration: TimeInterval = 1, delay: TimeInterval = 0) {
self.alpha = 0
UIView.animate(withDuration: duration, delay: delay, options: [], animations: { self.alpha = 1 }, completion: nil)
}
}
Then use it like this:
var delay = 0.2
var duration = 0.5
for label in labels {
label.fadeIn(duration: duration, delay: delay)
delay += 0.2 // Increment delay
}

UIButton shows old title after animation

Can anyone tell me why this shows the old button title for a second when reappearing?
func showSuccess(success:Bool) {
if success == true {
self.stage = 2 //needed to determine what method is triggered on button action
UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseOut, animations: {
self.oldView.alpha = 0
}, completion: { finished in
self.textLabel.text = "new text new text new text"
self.actionButton.setTitle("BAR", forState: .Normal)
self.reappearView(self)
})
}
}
func reappearView(sender:AnyObject) {
self.oldView.layoutIfNeeded()
UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseOut, animations: {
self.oldView.alpha = 1
}, completion: { finished in
})
}
For some odd reason, the text change on the label field works fine as expected. The button will animate in with it's old title, then during the reappear animation - switch to the new title set in the completion handler of showSuccess().
I also tried to move all text changes as first part of reappearView() and then trigger the 2nd animation, same outcome.

How do I change the text of a UILabel between each animation of animateWithDuration with repeat?

I have an Array words containing ["alice", "bob", "charles"] and a UILabel label. I want label to repeatedly fade in and out, with a different word from words each time. If I put the text-changing code inside the animations block, it doesn’t execute, even though the fading works as expected (the completion block is run only when something stops the repetition):
label.alpha = 0
UIView.animateWithDuration(4, delay: 0, options: .Autoreverse | .Repeat, animations: {
self.label.text = nextWord()
self.label.alpha = 1
}, completion: {_ in
NSLog("Completed animation")
})
What’s a good way to fix this? Thanks.
surely not the most elegant but working solution:
#IBOutlet weak var label: UILabel!
let words = ["Is", "this", "what", "you", "want?"]
var currentIndex = -1
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
showNextWord()
}
func showNextWord() {
if currentIndex == words.count - 1 {
currentIndex = -1
}
UIView.animateWithDuration(1, delay: 1, options: UIViewAnimationOptions(0), animations: { () -> Void in
self.label.alpha = 0.0
}) { (_) -> Void in
self.label.text = self.words[++self.currentIndex]
UIView.animateWithDuration(1, animations: { () -> Void in
self.label.alpha = 1.0
}, completion: { (_) -> Void in
self.showNextWord()
})
}
}
You could construct this as a keyframe animation. That way, you can chain three animations together (one for each word) and repeat that entire chain.
Alternatively (this is what I would probably do), put one animation into a method of its own, and in the completion block, add a short delay and call the method - thus creating a perpetual loop. The loop creates the repetition, but each animation is just one animation, so now you can progress through the array on successive calls. So the structure (pseudo-code) would look like this:
func animate() {
let opts = UIViewAnimationOptions.Autoreverse
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
// animate one fade in and out
}, completion: {
_ in
delay(0.1) {
// set the next text
self.animate() // recurse after delay
}
})
}

using animatewithduration to move image view

I am trying to move an image multiple times. This is how i tried implementing it.
override func viewDidAppear(animated: Bool) {
UIView.animateWithDuration(1, animations: { () -> Void in
self.sattelite.center.x = 50
self.sattelite.center.y = 100
self.sattelite.center.x = 50
self.sattelite.center.y = 50
self.sattelite.center.x = 50
self.sattelite.center.y = 300
})
}
The animatewithduration method however only executes one of the movements. Is there any way to animate all three movements of the image view? Thanks.
you can easily chain animations ; start another at completion :
Here an illustration :
//1st animation
UIView.animateWithDuration(1.0,
delay: 0.0,
options: .CurveEaseInOut | .AllowUserInteraction,
animations: {
//some code ex : view.layer.alpha = 0.0
},
completion: { finished in
//second animation at completion
UIView.animateWithDuration(1.0
, delay: 0.0,
, options: .CurveEaseInOut | .AllowUserInteraction,
, animations: { () -> Void in
//some code ex : view.layer.alpha = 1.0
}
, completion: { finished in
//third animation at completion
UIView.animateWithDuration(1.0,
delay: 0.0,
options: .CurveEaseInOut | .AllowUserInteraction,
animations: {
//some code ex : view.layer.alpha = 0.0
},
completion: { finished in
//FINISH : 3 animations!!!
})
})
})
You need to either chain the animations with completion or use a Key Frame animation
Use the animation version which has completion:
class func animateWithDuration(_ duration: NSTimeInterval,
animations animations: () -> Void,
completion completion: ((Bool) -> Void)?)
Do the first animation only on animations and start the second in the completion. The third goes in the completion of the second.
So basically a chain of animations, where each starts when the previous finishes.

How to cancel previous animation when a new one is triggered?

I am writing a camera app, and have trouble with showing the focus square when user tap on the screen.
My code is (in swift):
self.focusView.center = sender.locationInView(self.cameraWrapper)
self.focusView.transform = CGAffineTransformMakeScale(2, 2)
self.focusView.hidden = false
UIView.animateWithDuration(0.5, animations: { [unowned self] () -> Void in
self.focusView.transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: { () -> Void in
self.focusView.alpha = 0.0
}, completion: { (finished) -> Void in
self.focusView.hidden = true
self.focusView.alpha = 1.0
})
})
However, if use tap the screen consecutively when the previous animation does not finish, the old and new animation will mix up and the focus view will behave strangely, for example it will disappear very quick.
Could anyone tell me how to cancel previous animation, especially the previous completion block?
You can user method removeAllAnimations to stop animation
Replace your code with below
self.focusView.center = sender.locationInView(self.cameraWrapper)
self.focusView.transform = CGAffineTransformMakeScale(2, 2)
self.focusView.hidden = false
self.focusView.layer.removeAllAnimations() // <<==== Solution
UIView.animateWithDuration(0.5, animations: { [unowned self] () -> Void in
self.focusView.transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: { () -> Void in
self.focusView.alpha = 0.0
}, completion: { (finished) -> Void in
self.focusView.hidden = true
self.focusView.alpha = 1.0
})
})
Reference : link
#Jageen solution is great, but I worked with UIStackView animation and there I needed additional steps. I have stackView with view1 and view2 inside, and one view should be visible and one hidden:
public func configureStackView(hideView1: Bool, hideView2: Bool) {
let oldHideView1 = view1.isHidden
let oldHideView2 = view2.isHidden
view1.layer.removeAllAnimations()
view2.layer.removeAllAnimations()
view.layer.removeAllAnimations()
stackView.layer.removeAllAnimations()
// after stopping animation the values are unpredictable, so set values to old
view1.isHidden = oldHideView1 // <- Solution is here
view2.isHidden = oldHideView2 // <- Solution is here
UIView.animate(withDuration: 0.3,
delay: 0.0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
view1.isHidden = hideView1
view2.isHidden = hideView2
stackView.layoutIfNeeded()
},
completion: nil)
}
In my case, I add the focusing indicator by using addSubview().
self.view.addSubview(square)
square is the UIView I defined in the function. So when the user tap the screen to focus, this function will add a square on subview.
And to cancel this animation when the next tap happen, I just simply use the removeFromSuperview() function, when this function called it removes the view from its superview which is the focusing square here.
filterView.subviews.forEach({ $0.removeFromSuperview() })
It's different from the method above to remove the animation, but remove the subview directly.
In my case, i just needed to set the value, i animated to concrete value.
For e.g.
if ([indexPath isEqual:self.currentPlayerOrderIndexPath])
{
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
cell.avatarImageV.transform = CGAffineTransformMakeScale(1.15,1.15);
}
completion:NULL];
}
else
{
cell.avatarImageV.transform = CGAffineTransformMakeScale(1.0, 1.0);

Resources