Swift:UIView.animate意外工作 [英] Swift: UIView.animate works unexpectedly

查看:94
本文介绍了Swift:UIView.animate意外工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一个记录按钮,它是一个带有我的应用程序的手势识别器的UIView。目前,我正在实现的功能是,单击视图时,我想缩小内部圆圈(红色圆圈),但是最终会导致意外的变换。我使用UIView.animate函数执行此操作,下面是我的相关代码:

I'm trying to make a "record button" which is a UIView with a gesture recognizer for my app. Right now I'm implementing the feature that when I click the view, I want to scale down the inner circle (the red one) however it ends up with an unexpected transformation. I use UIView.animate function to do this, and below is my relating code:

import UIKit

@IBDesignable
class RecordView: UIView {

    @IBInspectable
    var borderColor: UIColor = UIColor.clear {
        didSet {
            self.layer.borderColor = borderColor.cgColor
        }
    }

    @IBInspectable
    var borderWidth: CGFloat = 20 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }

    @IBInspectable
    var cornerRadius: CGFloat = 100 {
        didSet {
            layer.cornerRadius = cornerRadius
        }
    }

    private var fillView = UIView()

    private func setupFillView() {
        let radius = (self.cornerRadius - self.borderWidth) * 0.95
        fillView.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2))
        fillView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        fillView.layer.cornerRadius = radius
        fillView.backgroundColor = UIColor.red
        self.addSubview(fillView)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        setupFillView()
    }

    func didClick() {
        UIView.animate(withDuration: 1.0, animations: {
            self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
        }) { (true) in
            print()
        }
    }

}

在我的ViewController中,我有:

In my ViewController, I have:

@IBAction func recoreViewTapped(_ sender: UITapGestureRecognizer) {
        recordView.didClick()
    }

但是最终会产生以下效果:
https://media.giphy。 com / media / 3ohjUQrWt7vfIxzBrG / giphy.gif 。我是一个初学者,真的不知道我的代码有什么问题吗?

However it ends up with this effect: https://media.giphy.com/media/3ohjUQrWt7vfIxzBrG/giphy.gif. I'm a beginner and really don't know what's wrong with my code?

推荐答案

问题是您正在申请转换,再次触发 layoutSubviews ,因此重置了 fillView的框架 应用于转换后的视图。

The issue is that you're applying a transform, which is triggering the layoutSubviews again, so the resetting of the frame of fillView is being applied on the transformed view.

至少有两个选择:


  1. 您可以只转换整个 RecordButton 视图:

@IBDesignable
class RecordView: UIView {

    @IBInspectable
    var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }

    @IBInspectable
    var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } }

    private var fillView = UIView()

    override init(frame: CGRect) {
        super.init(frame: frame)

        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        configure()
    }

    private func configure() {
        fillView.backgroundColor = .red
        self.addSubview(fillView)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let radius = min(bounds.width, bounds.height) / 2  - borderWidth
        fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius),
                                size: CGSize(width: radius * 2, height: radius * 2))
        fillView.layer.cornerRadius = radius
    }

    func didClick() {
        UIView.animate(withDuration: 1.0, animations: {
            self.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
        }, completion: { _ in
            print("completion", #function)
        })
    }

}


  • 或者您也可以将 fillView 放在容器中,然后 fillView 的转换不会触发 layoutSubviews RecordView 的c>:

  • Or you could put the fillView inside a container, and then the transform of the fillView won't trigger the layoutSubviews of the RecordView:

    @IBDesignable
    class RecordView: UIView {
    
        @IBInspectable
        var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }
    
        @IBInspectable
        var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } }
    
        private var fillView = UIView()
        private var containerView = UIView()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            configure()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            configure()
        }
    
        private func configure() {
            fillView.backgroundColor = .red
            self.addSubview(containerView)
            containerView.addSubview(fillView)
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            containerView.frame = bounds
    
            let radius = min(bounds.width, bounds.height) / 2  - borderWidth
            fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius),
                                    size: CGSize(width: radius * 2, height: radius * 2))
            fillView.layer.cornerRadius = radius
        }
    
        func didClick() {
            UIView.animate(withDuration: 1.0, animations: {
                self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
            }, completion: { _ in
                print("completion", #function)
            })
        }
    
    }
    







  • 顺便说一下,还有一些其他小事情:


    By the way, a few other little things:


    • 正如您和我在其他地方讨论过的,配置(例如应该从 init 中调用添加子视图)。但是任何框架 / 边界或有条件的东西都应从 layoutSubviews 。

    • As you and I discussed elsewhere, the initial configuration (e.g. adding of the subview) should be called from init. But any frame/bounds contingent stuff should be called from layoutSubviews.

    完全不相关,但是参数传递给 UIView的 completion .animate(withDuration:animations:completion:)是一个布尔值,指示动画是否已完成完成。如果不打算使用该布尔值,则应使用 _ ,而不要使用(true)

    Completely unrelated, but the parameter passed to completion of UIView.animate(withDuration:animations:completion:) is a Boolean indicating whether the animation is finished or not. If you're not going to use that Boolean, you should use _, not (true).

    长期而言,我建议将与触摸 /手势相关的内容移到RecordButton中。我可以轻松地想象您变得更加幻想:例如一种是您触摸动画(收缩按钮以呈现按下氛围),另一种是抬高动画(例如,将按钮收缩并将圆形的记录按钮转换为方形的停止按钮)。然后,您可以让按钮在发生重大事件(例如,记录或停止识别)时通知视图控制器,但会降低视图控制器本身的复杂性。

    Longer term, I'd suggest moving the "touches"/gesture related stuff into the RecordButton. I can easily imagine you getting fancier: e.g. one animation as you "touch down" (shrink the button to render the "depress" vibe) and another for "lift up" (e.g. unshrink the button and convert the round "record" button to a square "stop" button). You can then have the button inform the view controller when something significant happens (e.g. record or stop recognized), but reduces complexity from the view controller itself.

    protocol RecordViewDelegate: class {
        func didTapRecord()
        func didTapStop()
    }
    
    @IBDesignable
    class RecordView: UIView {
    
        @IBInspectable
        var borderColor: UIColor = .clear { didSet { layer.borderColor = borderColor.cgColor } }
    
        @IBInspectable
        var borderWidth: CGFloat = 20 { didSet { layer.borderWidth = borderWidth; setNeedsLayout() } }
    
        weak var delegate: RecordViewDelegate?
    
        var isRecordButton = true
    
        private var fillView = UIView()
        private var containerView = UIView()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            configure()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            configure()
        }
    
        private func configure() {
            fillView.backgroundColor = .red
            self.addSubview(containerView)
            containerView.addSubview(fillView)
        }
    
        private var radius: CGFloat { return min(bounds.width, bounds.height) / 2  - borderWidth }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            containerView.frame = bounds
    
            fillView.frame = CGRect(origin: CGPoint(x: bounds.midX - radius, y: bounds.midY - radius),
                                    size: CGSize(width: radius * 2, height: radius * 2))
            fillView.layer.cornerRadius = isRecordButton ? radius : 0
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            UIView.animate(withDuration: 0.25) {
                self.fillView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
                self.fillView.backgroundColor = UIColor(red: 0.75, green: 0, blue: 0, alpha: 1)
            }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            if let touch = touches.first, containerView.bounds.contains(touch.location(in: containerView)) {
                if isRecordButton {
                    delegate?.didTapRecord()
                } else {
                    delegate?.didTapStop()
                }
                isRecordButton = !isRecordButton
            }
    
            UIView.animate(withDuration: 0.25) {
                self.fillView.transform = .identity
                self.fillView.backgroundColor = UIColor.red
                self.fillView.layer.cornerRadius = self.isRecordButton ? self.radius : 0
            }
    
        }
    }
    

    结果如下:

    < img src = https://i.stack.imgur.com/i8ce0.gif alt =在此处输入图片描述>

    这篇关于Swift:UIView.animate意外工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆