ios - 从顶部而不是固有大小的中心开始动画

标签 ios swift animation uiview intrinsic-content-size

我正在尝试让我的 View 从上到下动画化。目前,当更改我的标签文本时,在 nil 和一些“错误消息”之间,标签从其固有大小的中心开始动画,但我希望常规“标签”为“静态”并且只为错误标签设置动画。基本上,错误标签应该位于常规标签的正下方,错误标签应该根据其(固有)高度展开。这本质上是一个复选框。我想在用户尚未选中该复选框但正在尝试进一步操作时显示错误消息。代码只是解释问题的基本实现。我已经尝试为 containerview 调整 anchorPoint 和 contentMode,但它们似乎并不像我想的那样工作。抱歉,如果缩进很奇怪

import UIKit
class ViewController: UIViewController {

    let container = UIView()
    let errorLabel = UILabel()


    var bottomLabel: NSLayoutConstraint!
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(container)
        container.contentMode = .top
        container.translatesAutoresizingMaskIntoConstraints = false
        container.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        container.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true
        container.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        container.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true

        let label = UILabel()
        label.text = "Very long text that i would like to show to full extent and eventually add an error message to. It'll work on multiple rows obviously"
        label.numberOfLines = 0
        container.contentMode = .top
        container.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
        label.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true

        container.addSubview(errorLabel)
        errorLabel.setContentHuggingPriority(UILayoutPriority(300), for: .vertical)
        errorLabel.translatesAutoresizingMaskIntoConstraints = false
        errorLabel.topAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
        errorLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
        errorLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true

        bottomLabel = errorLabel.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor)
        bottomLabel.isActive = false

        errorLabel.numberOfLines = 0

        container.backgroundColor = .green
        let tapRecognizer = UITapGestureRecognizer()
        tapRecognizer.addTarget(self, action: #selector(onTap))
        container.addGestureRecognizer(tapRecognizer)
    }

    @objc func onTap() {

        self.container.layoutIfNeeded()
        UIView.animate(withDuration: 0.3, animations: {
            let active = !self.bottomLabel.isActive
            self.bottomLabel.isActive = active
            self.errorLabel.text = active ? "A veru very veru very veru very veru very veru very veru very veru very veru very long Error message" : nil


            self.container.layoutIfNeeded()
        })
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

最佳答案

我发现让动态多行标签以我想要的方式“动画化”有点困难 - 特别是当我想“隐藏”标签时。

一种方法:创建 2 个“错误”标签,一个叠加在另一个之上。使用“隐藏”标签来控制容器 View 上的约束。为变化设置动画时,容器 View 的边界将有效地“显示”和“隐藏”(显示/隐藏)“可见”标签。

这是一个示例,您可以直接在 Playground 页面中运行:

import UIKit
import PlaygroundSupport

class RevealViewController: UIViewController {

    let container = UIView()
    let staticLabel = UILabel()
    let hiddenErrorLabel = UILabel()
    let visibleErrorLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        // colors, just so we can see the bounds of the labels
        view.backgroundColor = .lightGray
        container.backgroundColor = .green
        staticLabel.backgroundColor = .yellow
        visibleErrorLabel.backgroundColor = .cyan

        // we don't want to see this label, so set its alpha to zero
        hiddenErrorLabel.alpha = 0.0

        // we want the Error Label to be "revealed" - so when it is has text it is initially "covered"
        container.clipsToBounds = true

        // all labels may be multiple lines
        staticLabel.numberOfLines = 0
        hiddenErrorLabel.numberOfLines = 0
        visibleErrorLabel.numberOfLines = 0

        // initial text in the "static" label
        staticLabel.text = "Very long text that i would like to show to full extent and eventually add an error message to. It'll work on multiple rows obviously"

        // add the container view to the VC's view
        // pin it to the sides, and 100-pts from the top
        // NO bottom constraint
        view.addSubview(container)
        container.translatesAutoresizingMaskIntoConstraints = false
        container.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0).isActive = true
        container.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        container.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true

        // add the static label to the container
        // pin it to the top and sides
        // NO bottom constraint
        container.addSubview(staticLabel)
        staticLabel.translatesAutoresizingMaskIntoConstraints = false
        staticLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
        staticLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
        staticLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true

        // add the "hidden" error label to the container
        // pin it to the sides, and  pin its top to the bottom of the static label
        // NO bottom constraint
        container.addSubview(hiddenErrorLabel)
        hiddenErrorLabel.translatesAutoresizingMaskIntoConstraints = false
        hiddenErrorLabel.topAnchor.constraint(equalTo: staticLabel.bottomAnchor).isActive = true
        hiddenErrorLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
        hiddenErrorLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true

        // add the "visible" error label to the container
        // pin its top, leading and trailing constraints to the hidden label
        container.addSubview(visibleErrorLabel)
        visibleErrorLabel.translatesAutoresizingMaskIntoConstraints = false
        visibleErrorLabel.topAnchor.constraint(equalTo: hiddenErrorLabel.topAnchor).isActive = true
        visibleErrorLabel.leadingAnchor.constraint(equalTo: hiddenErrorLabel.leadingAnchor).isActive = true
        visibleErrorLabel.trailingAnchor.constraint(equalTo: hiddenErrorLabel.trailingAnchor).isActive = true

        // pin the bottom of the hidden label ot the bottom of the container
        // now, when we change the text of the hidden label, it will
        // "push down / pull up" the bottom of the container view
        hiddenErrorLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true

        // add a tap gesture
        let tapRecognizer = UITapGestureRecognizer()
        tapRecognizer.addTarget(self, action: #selector(onTap))
        container.addGestureRecognizer(tapRecognizer)

    }

    var myActive = false

    @objc func onTap() {

        let errorText = "A veru very veru very veru very veru very veru very veru very veru very veru very long Error message"

        self.myActive = !self.myActive

        if self.myActive {

            // we want to SHOW the error message

            // set the error message in the VISIBLE error label
            self.visibleErrorLabel.text = errorText

            // "animate" it, with duration of 0.0 - so it is filled instantly
            // it will extend below the bottom of the container view, but won't be
            // visible yet because we set .clipsToBounds = true on the container
            UIView.animate(withDuration: 0.0, animations: {

            }, completion: {
                _ in

                // now, set the error message in the HIDDEN error label
                self.hiddenErrorLabel.text = errorText

                // the hidden label will now "push down" the bottom of the container view
                // so we can animate the "reveal"
                UIView.animate(withDuration: 0.3, animations: {
                    self.view.layoutIfNeeded()
                })

            })

        } else {

            // we want to HIDE the error message

            // clear the text from the HIDDEN error label
            self.hiddenErrorLabel.text = ""

            // the hidden label will now "pull up" the bottom of the container view
            // so we can animate the "conceal"
            UIView.animate(withDuration: 0.3, animations: {
                self.view.layoutIfNeeded()
            }, completion: {
                _ in

                // after its hidden, clear the text of the VISIBLE error label
                self.visibleErrorLabel.text = ""

            })

        }

    }

}

let vc = RevealViewController()
PlaygroundPage.current.liveView = vc

关于ios - 从顶部而不是固有大小的中心开始动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48748435/

相关文章:

ios - C4 在 subview 之间来回导航

ios - 选择“图像”或“拍照”并转到新的 ViewController

Swift 相当于这个类

ios - 我忘记了 APNs 证书,现在怎么办? (推送通知不起作用)

ios - 更改 UITextField 的内容并重新识别新行

ios - UIView 初始值设定项 swift Xcode 6 beta 5

ios - 当作为 Objective-C block 调用时,Swift 闭包崩溃

javascript - 使用 CSS 或 JavaScript 循环椭圆形对象

c# - XNA动画 Sprite 从透明到不透明

javascript - 悬停时继续 CSS3 关键帧动画/悬停时停止重复