I have UITableView that update rapidly via WebSocket and RXSwift. Every update will play flash animation. Everything works well in iOS11 - iOS14 but after the iOS15 update, the animation has weird behavior. It isn't play properly. It skip most of animation updates. Sometime it play animation in all rows at the same time.
Edited: I've got another issue; when I press on the button in the cell, the action didn't fire. It take a lot of click on it to make it fired, looks like it can't touch the button while updating. Sometime I press on button in cell 1 but the action fired as cell 2 context.
(Cell information was hidden for secret)
From the video, on the left was run on iOS11-iOS14. The animation works smoothly while on the right the animation was skipped.
The code to update the animation is:
func flash()->Observable<Void>{
return Observable.create { (observer) -> Disposable in
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: []){
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.background.alpha = 0
}
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5) {
self.background.alpha = 0.2
}
UIView.addKeyframe(withRelativeStartTime: 1, relativeDuration: 0.5) {
self.background.alpha = 0
}
}
return Disposables.create()
}
}
Call it like this. I didn't dispose someBehaviorRelay because it removes the animation
someBehaviorRelay.subscribe { [unowned self] value in
flash().subscribe().disposed(by: disposeBag)
}
And I reassign disposeBage when reuse
override func prepareForReuse() {
disposeBag = DisposeBag()
}
Is there any suggestion for me to solve this problem? Thank you.
UPDATE
I found the solution is use reconfigure instead of reloadData for UITableView
// if iOS15
let indexPaths = tableView.indexPathsForVisibleRows!
if !indexPaths.isEmpty {
tableView.reconfigureRows(at: indexPaths)
} else {
tableView.reloadData()
}
I found the solution already, updated in the question.
Related
In my application, i listed delivered notifications as sorted by date in tableView. If user tap to notifications from device notification screen, app highlights row. But before user presses notification on device main screen, if users scroll towards the end of the tableView, when user presses notification, app scrolls the tableView to the row with as an animated and also highlights the row.
But if users scrolled contentView to end of the tableView, highlight is not working. I think scroll to row animation function and highlight animation function working at same time.
How can i catch when tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) animation finished. If i know when scrollToRow animation finish, i can run highlight row code after.
Thanks.
DispatchQueue.main.async {
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
if let cell = self.tableView.cellForRow(at: indexPath) as? NewsItemCell {
let highlightView = UIView.init(frame: cell.contentView.frame)
highlightView.backgroundColor = .white
highlightView.alpha = 0.0
cell.contentView.addSubview(highlightView)
print(indexPath)
UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseIn) {
highlightView.alpha = 1.0
} completion: { Bool in
UIView.animate(withDuration: 1.5, delay: 0, options: .curveEaseOut) {
highlightView.alpha = 0.0
} completion: { Bool in
highlightView.removeFromSuperview()
}
}
}
}
A UITableView is a subclass of UIScrollView.
You should be able to implement the UIScrollViewDelegate method scrollViewDidEndScrollingAnimation(_:) in your table view delegate (usually the owning view controller) and then highlight the row in your implementation of that method. (You'll probably need to add logic and state variables to track the fact that you are in this situation.)
Edit:
Note that as mentioned in the answer from #trndjc linked by HangerRash, you should your follow-on animation code from a call to Dispatch.main.async(). (That will help avoid stutters in the animations.)
From that answer:
Second, despite the fact that the method is called on the main thread, if you're running a subsequent animation here (one after the scroll has finished), it will still hitch (this may or may not be a bug in UIKit). Therefore, simply dispatch any follow-up animations back onto the main queue which just ensures that the animations will begin after the end of the current main task (which appears to include the scroll-to-row animation). Doing this will give you the appearance of a true completion.
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
DispatchQueue.main.async {
// execute subsequent animation here
}
}
Hi I checked all related question but I cannot solve my problem , I have an animate function like this;
func startScrollSlideShow(sliderValue: Float) {
if (scrollView.contentOffset.y <= (scrollView.contentSize.height - scrollView.frame.size.height)){
//reach bottom
UIScrollView.animate(withDuration: TimeInterval(sliderValue), delay: 0.5, options: .allowUserInteraction, animations: {
self.scrollView.contentOffset.y += 3.5
}) { (completion) in
self.startScrollSlideShow(sliderValue: Float(UserDefaults.standard.string(forKey: "sliderValue")!)!)
}
}else{
return
}
}
and I have a button for stop this animate
#objc func stopButton(sender: UIButton){
self.scrollView.layer.removeAllAnimations()
self.scrollView.layer.layoutIfNeeded()
}
my stop button does not stop my animate function ☹️
You need UIViewPropertyAnimator class.
See this Documentation.
https://developer.apple.com/documentation/uikit/uiviewpropertyanimator
A property animator gives you programmatic control over the timing and execution of the animations. Specifically, you can start, pause, resume, and stop animations.
-Apple Documentation-
Try this:
UIView.setAnimationsEnabled(false)
I'm animating a view to move as the user pans the screen. I have kept a threshold after which the view will animate to a default position.
The problem currently is that the completion handler of the animate method which resets the view to a position is called before the duration. The animation seems to be happening abruptly instead of over a duration of time.
// Pan gesture selector method
#objc func panAction(for panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began:
//Began code
case changed:
if (condition) {
print("IF")
//Change constraint constant of customView
animate(view: customView)
} else if (condition) {
print("ELSE IF")
//Change constraint constant of customView
animate(view: customView)
} else {
//Change constraint constant of customView
print("ELSE")
view.layoutIfNeeded()
}
case .ended:
//Ended code
default:
break
}
}
The animate method:
func animate(view: UIView) {
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseOut, animations: {
view.layoutIfNeeded()
}, completion: { (finished) in
if finished {
flag = false
}
})
}
The flag is being set immediately rather than after 3 seconds.
The o/p i get while panning and crossing threshold.
ELSE
ELSE
ELSE
ELSE
IF
Edit: I am an idiot. I did not call layoutIfNeeded() on the superView.
Your gesture sends several events while the gesture is happening, and as you call your UIView.animate() code multiple times the new value supersedes the previous one.
Try adding the animation option .beginFromCurrentState:
UIView.animate(withDuration: 0.5, delay: 0, options: [.beginFromCurrentState,.allowAnimatedContent,.allowUserInteraction], animations: {
view.layoutIfNeeded()
}) { (completed) in
...
}
And expect your completed to be called multiple times with the completed == false as the gesture is progressing.
Edit: Your issue may also be related to calling layoutIfNeeded() on the wrong view, possibly try to call this on the viewController.view ?
I solved this. I was not calling layoutIfNeeded on the superview.
I have a CollectionView and I want to create an animation inside the CollectionViewCell selected by the user. I chose to use animateKeyframesWithDuration because I want to create a custom animation step by step. My code looks like this:
func animate() {
UIView.animateKeyframesWithDuration(1.0, delay: 0.0, options: .AllowUserInteraction, animations: { () -> Void in
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.5, animations: { () -> Void in
// First step
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: { () -> Void in
// Second step
})
}) { (finished: Bool) -> Void in
if self.shouldStopAnimating {
self.loadingView.layer.removeAllAnimations()
} else {
self.animate()
}
}
}
This is executed inside the custom CollectionViewCell when it is selected.
The problem is that I want to force stop the animation immediately at some certain point. But when I do that, the animation doesn't fully stop, it just moves the remaining animation on a different cell (probably the last reused cell?)
I can't understand why this is happening. I have tried different approaches but none of them successfully stop the animation before normally entering the completion block
Does anyone have any idea about this?
Instead of removing the animations from the layer you could try adding another animation with a very short duration that sets the view properties that you want to stop animating.
Something like this:
if self.shouldStopAnimating {
UIView.animate(withDuration: 0.01, delay: 0.0, options: UIView.AnimationOptions.beginFromCurrentState, animations: { () -> Void in
//set any relevant properties on self.loadingView or anything else you're animating
//you can either set them to the final animation values
//or set them as they currently are to cancel the animation
}) { (completed) -> Void in
}
}
This answer may also be helpful.
I am trying clear the data in a UITableView with some quick animation, I am using the below code, which works fine in iOS8.
However when I run it on iOS7 it only runs the block after completion , but not the first animation code (fade out), so the animation looks very bad, the tableview disappears suddenly and returns back with animation.
any idea what is wrong here? what is the problem with it in iOS7?
UIView.animateWithDuration (0.5, animations: {
self.tableView.transform = CGAffineTransformMakeScale(0.2, 0.2)
self.tableView.alpha = 0
}, completion: { (value: Bool) in
UIView.animateWithDuration (0.5, animations: {
self.tableView.reloadData()
self.tableView.transform = CGAffineTransformMakeScale(1.0, 1.0)
self.tableView.alpha = 1
println ("animation done")
})
})
Try removing UIView.animateWithDuration (0.5, animations: { block inside completion block and check