我正试图让我的观点从上到下进行动画制作。目前,当更改我的标签文本时,在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.
}
}
答案 0 :(得分:0)
我发现让动态多线标签变得有点困难"动画"以我想要的方式 - 特别是当我想要"隐藏"标签。
一种方法:创建2"错误"标签,一个覆盖在另一个之上。使用"隐藏" label来控制容器视图的约束。在动画更改时,容器视图的界限将有效地显示"并且"隐藏" (显示/隐藏)"可见"标签
以下是一个示例,您可以直接在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
答案 1 :(得分:0)
因此,由于它是我想要创建的控件(复选框),在这种情况下带有错误消息,我根据边界直接操作帧。因此,为了使其正常工作,我使用了覆盖intrinsicContentSize和layoutSubviews以及一些小额外内容的组合。该类包含的内容多于提供的内容,但提供的代码应该有助于解释我使用的方法。
open class Checkbox: UIView {
let imageView = UIImageView()
let textView = ThemeableTapLabel()
private let errorLabel = UILabel()
var errorVisible: Bool = false
let checkboxPad: CGFloat = 8
override open var bounds: CGRect {
didSet {
// fixes layout when bounds change
invalidateIntrinsicContentSize()
}
}
open var errorMessage: String? {
didSet {
self.errorVisible = self.errorMessage != nil
UIView.animate(withDuration: 0.3, animations: {
if self.errorMessage != nil {
self.errorLabel.text = self.errorMessage
}
self.setNeedsLayout()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}, completion: { success in
if self.errorMessage == nil {
self.errorLabel.text = nil
}
})
}
}
func checkboxSize() -> CGSize {
return CGSize(width: imageView.image?.size.width ?? 0, height: imageView.image?.size.height ?? 0)
}
override open func layoutSubviews() {
super.layoutSubviews()
frame = bounds
let imageFrame = CGRect(x: 0, y: 0, width: checkboxSize().width, height: checkboxSize().height)
imageView.frame = imageFrame
let textRect = textView.textRect(forBounds: CGRect(x: (imageFrame.width + checkboxPad), y: 0, width: bounds.width - (imageFrame.width + checkboxPad), height: 10000), limitedToNumberOfLines: textView.numberOfLines)
textView.frame = textRect
let largestHeight = max(checkboxSize().height, textRect.height)
let rect = errorLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: bounds.width, height: 10000), limitedToNumberOfLines: errorLabel.numberOfLines)
//po bourect = rect.offsetBy(dx: 0, dy: imageFrame.maxY)
let errorHeight = errorVisible ? rect.height : 0
errorLabel.frame = CGRect(x: 0, y: largestHeight, width: bounds.width, height: errorHeight)
}
override open var intrinsicContentSize: CGSize {
get {
let textRect = textView.textRect(forBounds: CGRect(x: (checkboxSize().width + checkboxPad), y: 0, width: bounds.width - (checkboxSize().width + checkboxPad), height: 10000), limitedToNumberOfLines: textView.numberOfLines)
let rect = errorLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: bounds.width, height: 10000), limitedToNumberOfLines: errorLabel.numberOfLines)
let errorHeight = errorVisible ? rect.height : 0
let largestHeight = max(checkboxSize().height, textRect.height)
return CGSize(width: checkboxSize().width + 200, height: largestHeight + errorHeight)
}
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
//...
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.numberOfLines = 0
contentMode = .top
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(checkboxTap(sender:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapGesture)
addSubview(errorLabel)
errorLabel.contentMode = .top
errorLabel.textColor = .red
errorLabel.numberOfLines = 0
}
}