I do widget. I need to changed cell height with animation. When click "show more" go extra controls.
enter image description here
I wrote this code
tableView.beginUpdates()
showModes(alpha: 1, duration: 0.2, delay: 0.1)
tableView.endUpdates()
func showModes(alpha: CGFloat, duration: CGFloat, delay: CGFloat) {
if tableView.numberOfRows(inSection: 0) != 0 {
for i in 0...tableView.numberOfRows(inSection: 0)-1 {
let indexPath = IndexPath(row: i, section: 0)
if let cell = tableView.cellForRow(at: indexPath) as? WidgetCell {
UIView.animate(withDuration: TimeInterval(duration), delay: TimeInterval(delay) ,animations: {
if alpha == 0 {
cell.favoriteConstraint.constant = -45
} else {
cell.favoriteConstraint.constant = 10
}
cell.favoriteMode.alpha = alpha
self.tableView.layoutIfNeeded()
})
}
}
}
}
But it does not work smoothly. it dramatically twitches up and down. How do I get this to work smoothly, for example, in the weather widget by Apple?
I can't recognise what is a problem definetly because you posted only part of your code. But I can suggest several checks to do
Did you set self-sizing cells for UITableView? AtableView.rowHeight = UITableViewAutomaticDimension;
Did you added constraint (favoriteConstraint) to cell's contentView
Also when animating constraint changes you don't need to change it inside animation closure.
The standard code looks like
cell.favoriteConstraint.constant = newValue
UIView.animateWithDuration(0.3) {
cell.contentView.layoutIfNeeded()
}
Also you don't need to wrap your method call. This must be ok:
showModes(alpha: 1, duration: 0.2, delay: 0.1)
tableView.beginUpdates()
tableView.endUpdates()
Related
I have been using the following UIView extension for quite a while to fade text in and out. I have been trying to figure out how to implement this with SwiftUI but so far haven't found anything that exactly addresses this for me. Any help would be greatly appreciated.
extension UIView {
func fadeKey() {
// Move our fade out code from earlier
UIView.animate(withDuration: 3.0, delay: 2.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.alpha = 1.0 // Instead of a specific instance of, say, birdTypeLabel, we simply set [thisInstance] (ie, self)'s alpha
}, completion: nil)
}
func fadeIn1() {
// Move our fade out code from earlier
UIView.animate(withDuration: 1.5, delay: 0.5, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.alpha = 1.0 // Instead of a specific instance of, say, birdTypeLabel, we simply set [thisInstance] (ie, self)'s alpha
}, completion: nil)
}
I assume this is what you wanted. Try this below code:
struct FadeView: View {
#State var isClicked = false
#State var text = "Faded Text"
var body: some View {
VStack {
Text(text) //fade in and fade out
.opacity(isClicked ? 0 : 1)
Button("Click to animate") {
withAnimation {
isClicked.toggle()
}
}
}
}
}
You could use withAnimation function and manipulate the duration, options and delay
struct ContentView: View {
#State var isActive = false
var body: some View {
VStack {
Button("Fade") {
withAnimation(.easeIn(duration: 3.0).delay(2)){
isActive.toggle()
}
}
Rectangle()
.frame(width: 222.0, height: 222.0)
.opacity(isActive ? 0 : 1)
}
}
}
Xcode swift 4
I have some views that are added dynamically in code one below another.
Each new view top anchor is connected to previous view bottom anchor.
And each view have a button that make view to expand/collapse with animation. Here is button code :
let fullHeight : CGFloat = 240
let smallHeight : CGFloat = 44
let currentHeigth = rootView.frame.size.height //I use this to get current height and understand expanded view or not
let heighCons = rootView.constraints.filter //I use this to deactivate current height Anchor constraint
{
$0.firstAttribute == NSLayoutAttribute.height
}
NSLayoutConstraint.deactivate(heighCons)
rootView.layoutIfNeeded()
if currentHeigth == smallHeight
{
rootView.heightAnchor.constraint(equalToConstant: fullHeight).isActive = true
rootView.setNeedsLayout()
UIView.animate(withDuration: 0.5)
{
rootView.layoutIfNeeded() //animation itself
}
}
else
{
rootView.heightAnchor.constraint(equalToConstant: smallHeight).isActive = true
rootView.setNeedsLayout()
UIView.animate(withDuration: 0.5)
{
rootView.layoutIfNeeded() //animation itself
}
}
This all works perfectly but i have a problem : view that below current expanding view changes it y position immediately with no animation. Its just jumping to previous view bottom anchor, that would be active after animation finish.
So my question is :
1) what is the right way to make height constraint animation, when views are connected to each other by bottom/top animation?
2) my goal is just to make a view that would expand/collapse on button click, maybe i should do it another way?
Here's an approach using Visiblity Gone Extension
extension UIView {
func visiblity(gone: Bool, dimension: CGFloat = 0.0, attribute: NSLayoutAttribute = .height) -> Void {
if let constraint = (self.constraints.filter{$0.firstAttribute == attribute}.first) {
constraint.constant = gone ? 0.0 : dimension
self.layoutIfNeeded()
self.isHidden = gone
}
}
}
Usage
expanview.visiblity(gone: true,dimension: 0)
Example
#IBOutlet weak var msgLabel: UILabel!
#IBOutlet weak var expanview: UIView!
#IBAction func toggleCollapisbleView(_ sender: UIButton) {
if sender.isSelected{
sender.isSelected = false
expanview.visiblity(gone: false,dimension: 128)
sender.setTitle("Collapse",for: .normal)
}
else{
sender.isSelected = true
expanview.visiblity(gone: true,dimension: 0)
sender.setTitle("Expand",for: .normal)
msgLabel.text = "Visiblity gone"
}
}
How can I make a effect in swift similar to this:
I want the animation to loop forever.
For iOS
UIViewAnimationOptions set provides different handy options to achieve a combination of beautiful and complex animations. For your particular scenario you will require two of the options.
UIViewAnimationOptions.Repeat
UIViewAnimationOptions.AutoReverse
Check out the code below for implementation.
Code:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = UIColor.blueColor()
self.view.addSubview(view)
UIView.animateWithDuration(1,
delay: 0,
options: [UIViewAnimationOptions.Autoreverse, UIViewAnimationOptions.Repeat],
animations: {
view.backgroundColor = UIColor.clearColor()
},
completion: nil)
}
}
Explanation:
I have created a view with a specific frame for demo purpose.
The part you are interested in is the UIView.animateWithDuration method. Notice that I have provided an array [UIViewAnimationOptions.AutoReverse, UIViewAnimationOptions.Repeat] in the options parameter.
These two options are enough to achieve a smooth and forever looping animation like below.
https://s3.amazonaws.com/uploads.hipchat.com/37040/1764070/6Iow7n7WiWf6Naz/autoReverse.gif
If you don't want to reverse the animation, just remove UIViewAnimationOptions.AutoReverse from the array in the options parameter. You will get an animation like this.
https://s3.amazonaws.com/uploads.hipchat.com/37040/1764070/8fyRUlzqNHSQI47/noreverse.gif
For iOS
let viewSquare be the name of the blue square in your question.
UIView.animate(withDuration: 0.5, delay: 0, options: [.repeat,.autoreverse], animations: {
viewSquare.alpha = 0.0
}, completion: nil)
Swift 5.1
let duration = 0.5
func fadeIn(finished: Bool) {
UIView.animate(withDuration: self.duration, delay: 0,
options: [.curveEaseInOut],
animations: { self.tftMap.alpha = 1 }, completion: self.fadeOut)
}
func fadeOut(finished: Bool) {
UIView.animate(withDuration: self.duration, delay: 0,
options: [.curveEaseInOut],
animations: { self.tftMap.alpha = 0 }, completion: self.fadeIn)
}
I assume you are programming for iOS.
Play around with the duration to see what suits you best:
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
let duration = 0.5
override func viewDidLoad() {
super.viewDidLoad()
self.fadeOut(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func fadeIn(finished: Bool) {
UIView.animateWithDuration(self.duration, delay: 0, options: [.CurveEaseInOut], animations: { self.myView.alpha = 1 } , completion: self.fadeOut)
}
func fadeOut(finished: Bool) {
UIView.animateWithDuration(self.duration, delay: 0, options: [.CurveEaseInOut], animations: { self.myView.alpha = 0 } , completion: self.fadeIn)
}
}
Swift 5
This worked well for me. I wanted to animate a continuous fade in and out of a label, which I placed inside the "cardHeaderView" UIView.
#IBOutlet weak var cardHeaderView: UIView!
Place this inside the "viewDidAppear". I went with a delay of zero so the animation would start right away.
fadeViewInThenOut(view: cardHeaderView, delay: 0)
Here is the function.
func fadeViewInThenOut(view : UIView, delay: TimeInterval) {
let animationDuration = 1.5
UIView.animate(withDuration: animationDuration, delay: delay, options: [UIView.AnimationOptions.autoreverse, UIView.AnimationOptions.repeat], animations: {
view.alpha = 0
}, completion: nil)
}
Swift 5
Fade in 2 seconds
pause 2 seconds,
fade out 2 seconds
Repeat
func startAnimation() {
UIView.animateKeyframes(withDuration: 6.0,
delay: 0,
options: [.repeat, .autoreverse, .calculationModeLinear]) {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.165) { [weak self] in
self?.view1.alpha = 0.0
}
UIView.addKeyframe(withRelativeStartTime: 0.165, relativeDuration: 0.165) { [weak self] in
self?.view2.alpha = 1.0
}
UIView.addKeyframe(withRelativeStartTime: 0.66, relativeDuration: 0.165) { [weak self] in
self?.view2.alpha = 0.0
}
ctartTime: 0.825, relativeDuration: 0.165) { [weak self] in
self?.view1.alpha = 1.0
}
}
}
Please note that when your animation is at Alpha == 0.0 the item is not interactable! You will have to add to .allowUserInteraction as an option
If you want repeatable fade animation you can do that by using CABasicAnimation like below :
###First create handy UIView extension :
extension UIView {
enum AnimationKeyPath: String {
case opacity = "opacity"
}
func flash(animation: AnimationKeyPath ,withDuration duration: TimeInterval = 0.5, repeatCount: Float = 5){
let flash = CABasicAnimation(keyPath: AnimationKeyPath.opacity.rawValue)
flash.duration = duration
flash.fromValue = 1 // alpha
flash.toValue = 0 // alpha
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
flash.autoreverses = true
flash.repeatCount = repeatCount
layer.add(flash, forKey: nil)
}
}
###How to use it:
// You can use it with all kind of UIViews e.g. UIButton, UILabel, UIImage, UIImageView, ...
imageView.flash(animation: .opacity, withDuration: 1, repeatCount: 5)
titleLabel.flash(animation: .opacity, withDuration: 1, repeatCount: 5)
In my application I have several fades. I create them with Core Animation, using syntax similar to what is below. Is there a less verbose syntax I could use?
CATransaction.begin()
let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 0
fade.toValue = 1
fade.duration = 0.35
fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
fade.fillMode = kCAFillModeForwards
fade.removedOnCompletion = false
CATransaction.setCompletionBlock({
...
})
self.addAnimation(fade, forKey: nil)
CATransaction.commit()
Sure. You can make your own:
extension CABasicAnimation
{
convenience init(_ keyPath: String, from: AnyObject?, to: AnyObject?, duration: CFTimeInterval = 0.3)
{
self.init(keyPath: keyPath)
fromValue = from
toValue = to
self.duration = duration
}
}
extension CALayer
{
func addAnimation(animation: CAAnimation, forKey key: String?, completion: Void -> Void)
{
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
addAnimation(animation, forKey: key)
CATransaction.commit()
}
}
// later...
let fade = CABasicAnimation("opacity", from: 0, to: 1, duration: 0.35)
self.addAnimation(fade, forKey: nil) {
// ...
}
As an alternative to #jtbandes' excellent answer (voted), in many cases you can use a much simpler UIView animation:
UIView.animateWidhDuration(.35
animations:
{
myView.alpha = 0
}
completion:
{
(finshed) in
//your completion code here
})
EDIT:
I see from your comment that you're developing for OS X. That requires a different technique, using the animation proxy for your view:
myView.animator.setAlphaValue(0.0)
And if you want it to use a different duration than the default 0.25 second duration, you need to set that in an animation context group:
NSAnimationContext.currentContext().beginGrouping()
NSAnimationContext.currentContext().setDuration(0.35)
myView.animator.setAlphaValue(0.0)
NSAnimationContext.currentContext().endGrouping()
I believe that unlike iOS, NSViews don't have a native alpha setting, and setAlphaValue actually manipulates the alpha value of the view's layer. In that case you will probably need to set the view as layer-backed.
For some reason after my first call of a serious of animation blocks, the animation seems to be faster, not sure if this is a bug or something i've done wrong but i'm sure someone call tell me.. i've made a UIView subclass to handle this.
import UIKit
import QuartzCore
class GBPopupController: UIView {
var originalContainerCenterY = CGFloat()
#IBOutlet var continerConstraintCenterY: NSLayoutConstraint!
#IBOutlet var containerConstraintCenterX: NSLayoutConstraint!
var startingCenter = CGPoint()
#IBOutlet var contentView: UIView!
#IBOutlet var button: UIButton!
override func layoutSubviews() {
super.layoutSubviews()
contentView.layer.cornerRadius = 10
button.layer.cornerRadius = 10
}
override func didMoveToSuperview() {
self.beginViewAnimations()
}
func animatePopupIn() {
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.7, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.contentView.layer.transform = CATransform3DIdentity
}, completion: {finished in
})
}
func beginViewAnimations() {
var transform = CATransform3DIdentity;
transform = CATransform3DMakeTranslation(0, -self.frame.size.height, 0)
self.contentView.layer.transform = transform
UIView.animateWithDuration(0.4, delay: 0.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
self.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
}, completion: {finished in
self.animatePopupIn()
})
}
func removeViewFromSuperView() {
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
var transform = CATransform3DIdentity;
transform = CATransform3DMakeTranslation(0, -self.frame.size.height, 0)
self.contentView.layer.transform = transform
}, completion: {finished in
UIView.animateWithDuration(0.4, delay: 0.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
self.backgroundColor = UIColor.clearColor()
}, completion: {finished in
self.removeViewAnimation()
})
})
}
func removeViewAnimation() {
self.removeFromSuperview()
}
My comment is too long so I answer here. I don't know if it can help you but here's what I did.
I use autolayout and so in the viewdidload(), dimensions are not yet initialized. They are based on a 600x600 size screen (meaning any width, any height). Screen size is picked up once the first action is done (like pushing a button in my case).
This is why I call my animation for the first time at this first action, just to put my frame at the good place. Then the rest is the same. But it kind of initialize my view with the right frame (size and position). After that, I don't have this problem and my animation is also good.
It's like I was calling it twice the first time, one to initialize and one just to play the animation.
I made some research and I found that it could be a problem with the layer which is different from the frame and which is initialized with a different animation from the one you choose. That's why I didn't have the same animation the first time.
Hope it will help you.