所以我想制作一个按钮按下时动画以转到一个圆圈,然后能够将按钮发送回其原始状态。这是我当前的动画,正如你所看到的,我已经完成一半了。
您也可以看到我在这里遇到了多个问题。首先,当我设置新约束时,X 约束不会将圆放置在父视图的中间。然后我最初的想法是,当我调用重置函数时,我也会传递视图的原始约束,但这不起作用。
我的想法是,当我使用它时,我将放置一个 UIView,然后在该视图中放置按钮,这样我就可以操纵它的约束。如果我在 UIStackView 中放置一个按钮,也会出现这种情况。
extension UIButton {
func animateWhileAwaitingResponse(showLoading: Bool, originalConstraints: [NSLayoutConstraint]) {
let spinner = UIActivityIndicatorView()
let constraints = [
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45),
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: self.superview, attribute: .top, multiplier: 1, constant: 4),
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: self.superview, attribute: .bottom, multiplier: 1, constant: 8),
NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
NSLayoutConstraint(item: spinner, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45)
]
if showLoading {
NSLayoutConstraint.deactivate(self.constraints)
self.translatesAutoresizingMaskIntoConstraints = false
spinner.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(spinner)
self.superview?.addConstraints(constraints)
spinner.color = .white
spinner.startAnimating()
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.setTitleColor(.clear, for: .normal)
self.layer.cornerRadius = 22.5
self.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
NSLayoutConstraint.deactivate(self.constraints)
self.setTitleColor(.white, for: .normal)
self.superview?.addConstraints(originalConstraints)
NSLayoutConstraint.activate(originalConstraints)
self.layer.cornerRadius = 0
for subview in self.subviews where subview is UIActivityIndicatorView {
subview.removeFromSuperview()
}
self.layoutIfNeeded()
}, completion: nil)
}
}
}
我已经更新了您的按钮扩展代码,如下所示,即添加和删除动画约束。
extension UIButton {
func animateWhileAwaitingResponse(showLoading: Bool, originalConstraints: [NSLayoutConstraint]) {
let spinner = UIActivityIndicatorView()
spinner.isUserInteractionEnabled = false
// Constraints which will add in supper view
let constraints = [
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: self.superview, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
NSLayoutConstraint(item: spinner, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45)
]
// Constrains which will add in button
let selfCostraints = [
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45),
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
]
// Keeping this outside of condition due to adding constrains programatically.
self.translatesAutoresizingMaskIntoConstraints = false
spinner.translatesAutoresizingMaskIntoConstraints = false
if showLoading {
// Remove width constrains of button from superview
// Identifier given in storyboard constrains
self.superview?.constraints.forEach({ (constraint) in
if constraint.identifier == "buttonWidth" {
constraint.isActive = false
}
})
NSLayoutConstraint.deactivate(self.constraints)
self.addSubview(spinner)
self.superview?.addConstraints(constraints)
self.addConstraints(selfCostraints)
spinner.color = .white
spinner.startAnimating()
spinner.alpha = 0
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.setTitleColor(.clear, for: .normal)
self.layer.cornerRadius = 22.5
spinner.alpha = 1
self.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
for subview in self.subviews where subview is UIActivityIndicatorView {
subview.removeFromSuperview()
}
self.removeConstraints(selfCostraints)
NSLayoutConstraint.deactivate(self.constraints)
self.setTitleColor(.white, for: .normal)
self.superview?.addConstraints(originalConstraints)
NSLayoutConstraint.activate(originalConstraints)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
}
}
我向按钮添加了以下约束:
此外,还添加了按钮宽度约束的标识符以从超级中删除,这将增加原始约束的运行时间。
然后我通过宽度约束的出口以编程方式更改按钮的宽度:
@IBOutlet weak var const_btnAnimation_width : NSLayoutConstraint!
内部
viewDidLoad
方法
self.const_btnAnimation_width.constant = UIScreen.main.bounds.width - 40
其中 40 是前导和尾随空格的总和。
单击按钮
@IBAction func btnAnimationPressed(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
self.btnAnimation.animateWhileAwaitingResponse(showLoading: true, originalConstraints: sender.constraints)
} else {
self.btnAnimation.animateWhileAwaitingResponse(showLoading: false, originalConstraints: self.btnAnimationConstraints)
}
}
btnAnimationConstraints
是 NSLayoutConstraint
的数组,如下:
var btnAnimationConstraints = [NSLayoutConstraint]()
所以我只是在
viewDidAppear
方法中分配按钮的所有约束,如下所示:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.btnAnimationConstraints = self.btnAnimation.constraints
}
希望这对您有帮助。
输出:
我注意到您在第一个动画中设置了
self.translatesAutoresizingMaskIntoConstraints = false
,但没有在第二个动画中将其设置回 true
。
这可能是问题的根源:您需要在第二个动画期间设置
self.translatesAutoresizingMaskIntoConstraints = true
。
如果您在 Interface Builder 中(或最初创建按钮的任何地方)关闭
translatesAutoresizingMaskIntoConstraints
,并使用正常约束进行所有布局,可能会不会那么混乱。
我正在更新代码以设置动态按钮高度并根据按钮父高度管理圆圈。你可以用
按钮.框架.高度
画一个圆圈。
extension UIButton {
func animateWhileAwaitingResponse(showLoading: Bool, originalConstraints: [NSLayoutConstraint]) {
let spinner = UIActivityIndicatorView()
spinner.isUserInteractionEnabled = false
// Constraints which will add in supper view
let constraints = [
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: self.superview, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.frame.height),
NSLayoutConstraint(item: spinner, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: self.frame.height)
]
// Constrains which will add in button
let selfCostraints = [
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: self.frame.height),
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.frame.height),
]
// Keeping this outside of condition due to adding constrains programatically.
self.translatesAutoresizingMaskIntoConstraints = false
spinner.translatesAutoresizingMaskIntoConstraints = false
if showLoading {
// Remove width constrains of button from superview
// Identifier given in storyboard constrains
self.superview?.constraints.forEach({ (constraint) in
if constraint.identifier == "buttonWidth" {
constraint.isActive = false
}
})
NSLayoutConstraint.deactivate(self.constraints)
self.addSubview(spinner)
self.superview?.addConstraints(constraints)
self.addConstraints(selfCostraints)
spinner.color = .white
spinner.startAnimating()
spinner.alpha = 0
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.setTitleColor(.clear, for: .normal)
self.layer.cornerRadius = self.frame.height / 2
spinner.alpha = 1
self.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
for subview in self.subviews where subview is UIActivityIndicatorView {
subview.removeFromSuperview()
}
self.removeConstraints(selfCostraints)
NSLayoutConstraint.deactivate(self.constraints)
self.setTitleColor(.white, for: .normal)
self.superview?.addConstraints(originalConstraints)
NSLayoutConstraint.activate(originalConstraints)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
}
}
所以我做了一些改变,它对我来说很完美。我在界面生成器中为按钮设置了基本约束。
// StartAnimation
self.payButton.startAnimating(originalConstraints: sender.constraints)
// StopAnimation
self.payButton.startAnimating(false, originalConstraints: sender.constraints)
func startAnimating(_ showLoading: Bool = true, originalConstraints: [NSLayoutConstraint]) {
lazy var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false
activityIndicator.color = .white
activityIndicator.startAnimating()
activityIndicator.alpha = 0
return activityIndicator
}()
let spinnerConst = [
activityIndicator.widthAnchor.constraint(equalToConstant: 40.0),
activityIndicator.heightAnchor.constraint(equalToConstant: 40.0),
activityIndicator.centerXAnchor.constraint(equalTo: self.centerXAnchor)
]
let buttonConst = [
self.widthAnchor.constraint(equalToConstant: 40.0),
self.heightAnchor.constraint(equalToConstant: 40.0)
]
if showLoading {
NSLayoutConstraint.deactivate(originalConstraints)
self.addSubview(activityIndicator)
self.superview?.addConstraints(buttonConst)
self.addConstraints(buttonConst)
self.addConstraints(spinnerConst)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.layer.cornerRadius = 20.0
activityIndicator.alpha = 1
self.titleLabel?.alpha = 0
self.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
for subview in self.subviews where subview is UIActivityIndicatorView {
subview.removeFromSuperview()
}
self.removeConstraints(spinnerConst)
self.removeConstraints(buttonConst)
self.superview?.removeConstraints(buttonConst)
self.superview?.addConstraints(originalConstraints)
self.addConstraints(originalConstraints)
NSLayoutConstraint.activate(originalConstraints)
self.titleLabel?.alpha = 1
self.layer.cornerRadius = 6
self.layoutIfNeeded()
}, completion: nil)
}
}
如果有人正在寻找 Swift UI 代码。
import SwiftUI
struct ButtonContentView: View {
@State private var isButtonPressed = false
@State private var isLoading = false
var body: some View {
ZStack {
Button(action: {
withAnimation {
isButtonPressed.toggle()
isLoading.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation {
isButtonPressed.toggle()
isLoading.toggle()
}
}
}) {
withAnimation(.easeInOut) {
Text(isButtonPressed ? "" : "Press Me")
.foregroundColor(.white)
.frame(width: isButtonPressed ? 60 : 200, height: isButtonPressed ? 60 : 50)
.background(Color.blue)
.clipShape(isButtonPressed ? RoundedRectangle(cornerRadius: 50) : RoundedRectangle(cornerRadius: 10))
}
}
if isLoading {
CircularProgressView()
.opacity(isButtonPressed ? 1 : 0)
.scaleEffect(isButtonPressed ? 1 : 0)
}
}
}
}
struct CircularProgressView: View {
@State private var rotation = 0.0
var body: some View {
Circle()
.trim(from: 0.08, to: 1.0)
.stroke(style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round))
.foregroundColor(.white)
.rotationEffect(.degrees(rotation))
.frame(width: 40, height: 40)
.onAppear {
DispatchQueue.main.async {
withAnimation(.linear(duration: 1)
.repeatForever(autoreverses: false)) {
rotation = 360.0
}
}
}
}
}
struct ButtonContentView_previews: PreviewProvider {
static var previews: some View {
ButtonContentView()
}
}