在 Swift 中呈现旋转器的动画按钮

问题描述 投票:0回答:5

所以我想制作一个按钮按下时动画以转到一个圆圈,然后能够将按钮发送回其原始状态。这是我当前的动画,正如你所看到的,我已经完成一半了。

您也可以看到我在这里遇到了多个问题。首先,当我设置新约束时,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)

      }
   }
}
ios swift animation uibutton uianimation
5个回答
1
投票

我已经更新了您的按钮扩展代码,如下所示,即添加和删除动画约束。

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
}

希望这对您有帮助。

输出:


0
投票

我注意到您在第一个动画中设置了

self.translatesAutoresizingMaskIntoConstraints = false
,但没有在第二个动画中将其设置回
true

这可能是问题的根源:您需要在第二个动画期间设置

self.translatesAutoresizingMaskIntoConstraints = true

如果您在 Interface Builder 中(或最初创建按钮的任何地方)关闭

translatesAutoresizingMaskIntoConstraints
,并使用正常约束进行所有布局,可能会不会那么混乱。


0
投票

我正在更新代码以设置动态按钮高度并根据按钮父高度管理圆圈。你可以用

按钮.框架.高度

画一个圆圈。

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)
    }
}

}


0
投票

所以我做了一些改变,它对我来说很完美。我在界面生成器中为按钮设置了基本约束。

// 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)
                }
            }

0
投票

如果有人正在寻找 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()
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.