隐藏视图动画

在 iOS11中,UIStackView中隐藏动画的行为发生了变化,但是我在任何地方都找不到这个文档。

IOS10

iOS 10 animation

IOS11

iOS 11 animation

两者的代码都是这样的:

UIView.animate(withDuration: DiscoverHeaderView.animationDuration,
delay: 0.0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
clear.isHidden = hideClear
useMyLocation.isHidden = hideLocation
},
completion: nil)

如何在 iOS11上恢复以前的行为?

52793 次浏览

Just had the same issue. The fix is adding stackView.layoutIfNeeded() inside the animation block. Where stackView is the container of the items you're wishing to hide.

UIView.animate(withDuration: DiscoverHeaderView.animationDuration,
delay: 0.0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
clear.isHidden = hideClear
useMyLocation.isHidden = hideLocation
stackView.layoutIfNeeded()
},
completion: nil)

Not sure why this is suddenly an issue in iOS 11 but to be fair it has always been the recommended approach.

Swift 4 Extension:

// MARK: - Show hide animations in StackViews


extension UIView {


func hideAnimated(in stackView: UIStackView) {
if !self.isHidden {
UIView.animate(
withDuration: 0.35,
delay: 0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
self.isHidden = true
stackView.layoutIfNeeded()
},
completion: nil
)
}
}


func showAnimated(in stackView: UIStackView) {
if self.isHidden {
UIView.animate(
withDuration: 0.35,
delay: 0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
self.isHidden = false
stackView.layoutIfNeeded()
},
completion: nil
)
}
}
}

I want to share this function that is good for hiding and showing many views in UIStackView, because with all the code I used before didn't work smooth because one needs to removeAnimation from some layers:

extension UIStackView {
public func make(viewsHidden: [UIView], viewsVisible: [UIView], animated: Bool) {
let viewsHidden = viewsHidden.filter({ $0.superview === self })
let viewsVisible = viewsVisible.filter({ $0.superview === self })


let blockToSetVisibility: ([UIView], _ hidden: Bool) -> Void = { views, hidden in
views.forEach({ $0.isHidden = hidden })
}


// need for smooth animation
let blockToSetAlphaForSubviewsOf: ([UIView], _ alpha: CGFloat) -> Void = { views, alpha in
views.forEach({ view in
view.subviews.forEach({ $0.alpha = alpha })
})
}


if !animated {
blockToSetVisibility(viewsHidden, true)
blockToSetVisibility(viewsVisible, false)
blockToSetAlphaForSubviewsOf(viewsHidden, 1)
blockToSetAlphaForSubviewsOf(viewsVisible, 1)
} else {
// update hidden values of all views
// without that animation doesn't go
let allViews = viewsHidden + viewsVisible
self.layer.removeAllAnimations()
allViews.forEach { view in
let oldHiddenValue = view.isHidden
view.layer.removeAllAnimations()
view.layer.isHidden = oldHiddenValue
}


UIView.animate(withDuration: 0.3,
delay: 0.0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {


blockToSetAlphaForSubviewsOf(viewsVisible, 1)
blockToSetAlphaForSubviewsOf(viewsHidden, 0)


blockToSetVisibility(viewsHidden, true)
blockToSetVisibility(viewsVisible, false)
self.layoutIfNeeded()
},
completion: nil)
}
}
}

Hope this saves others a few hours of frustration.

Animating hiding AND showing multiple UIStackView subviews at the same time is a mess.

In some cases a change to .isHidden in animation blocks displays correctly only until the next animation, then .isHidden is ignored. The only reliable trick I found for this is to repeat the .isHidden instructions in the completion block of the animation.

    let time = 0.3


UIView.animate(withDuration: time, animations: {


//shows
self.googleSignInView.isHidden = false
self.googleSignInView.alpha = 1
self.registerView.isHidden = false
self.registerView.alpha = 1


//hides
self.usernameView.isHidden = true
self.usernameView.alpha = 0
self.passwordView.isHidden = true
self.passwordView.alpha = 0


self.stackView.layoutIfNeeded()


}) { (finished) in


self.googleSignInView.isHidden = false
self.registerView.isHidden = false
self.usernameView.isHidden = true
self.passwordView.isHidden = true
}

It's already mentioned in the comments of the accepted answer, but this was my problem and it's not in any of the answer here so:

Make sure to never set isHidden = true on a view that is already hidden. This will mess up the stack view.

According to jimpic's answer, I wrote a simple function and solved the problem I had in showing and hiding views in stackView with animation.

func hide(_ vu: UIView) {
if vu.isHidden == true {
return
} else {
vu.isHidden = true
}
}


func show(_ vu: UIView) {
if vu.isHidden == true {
vu.isHidden = false
} else {
return
}
}

Use the above function:

UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseOut], animations: {
self.hide(self.nameTextField)
})