Ios Swift:根据行数将对象推到标签下
我试图使材质设计文本字段的行为与自定义文本字段的行为相同。 我创建了一个从textfield继承的类,一切都很好。唯一的问题是在一个场景中。当我在文本字段下有一个对象时,我在文本字段下添加了错误标签。错误标签可能不止一行。因此它覆盖了textfield下的对象。但是,在材质设计库中,“文本”字段下的对象将根据错误标签的行数自动向下推 这是我的自定义文本字段代码:Ios Swift:根据行数将对象推到标签下,ios,swift,user-interface,uitextfield,material-design,Ios,Swift,User Interface,Uitextfield,Material Design,我试图使材质设计文本字段的行为与自定义文本字段的行为相同。 我创建了一个从textfield继承的类,一切都很好。唯一的问题是在一个场景中。当我在文本字段下有一个对象时,我在文本字段下添加了错误标签。错误标签可能不止一行。因此它覆盖了textfield下的对象。但是,在材质设计库中,“文本”字段下的对象将根据错误标签的行数自动向下推 这是我的自定义文本字段代码: import UIKit import RxSwift import RxCocoa class FloatingTextFiel
import UIKit
import RxSwift
import RxCocoa
class FloatingTextField2: UITextField {
var placeholderLabel: UILabel!
var line: UIView!
var errorLabel: UILabel!
let bag = DisposeBag()
var activeColor = Constants.colorBlue
var inActiveColor = UIColor(red: 84/255.0, green: 110/255.0, blue: 122/255.0, alpha: 0.8)
var errorColorFull = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 1.0)
//var errorColorParcial = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 0.5)
private var lineYPosition: CGFloat!
private var lineXPosition: CGFloat!
private var lineWidth: CGFloat!
private var lineHeight: CGFloat!
private var errorLabelYPosition: CGFloat!
private var errorLabelXPosition: CGFloat!
private var errorLabelWidth: CGFloat!
private var errorLabelHeight: CGFloat!
var maxFontSize: CGFloat = 14
var minFontSize: CGFloat = 11
let errorLabelFont = UIFont(name: "Lato-Regular", size: 12)
var animationDuration = 0.35
var placeholderText: String = "" {
didSet {
if placeholderLabel != nil {
placeholderLabel.text = placeholderText
}
}
}
var isTextEntrySecured: Bool = false {
didSet {
self.isSecureTextEntry = isTextEntrySecured
}
}
override func draw(_ rect: CGRect) {
//setUpUI()
}
override func awakeFromNib() {
setUpUI()
}
func setUpUI() {
if placeholderLabel == nil {
placeholderLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: 20))
self.addSubview(placeholderLabel)
self.borderStyle = .none
placeholderLabel.text = "Placeholder Preview"
placeholderLabel.textColor = inActiveColor
self.font = UIFont(name: "Lato-Regular", size: maxFontSize)
self.placeholderLabel.font = UIFont(name: "Lato-Regular", size: maxFontSize)
self.placeholder = ""
self.textColor = .black
setUpTextField()
}
if line == nil {
lineYPosition = self.frame.height
lineXPosition = -16
lineWidth = self.frame.width + 32
lineHeight = 1
line = UIView(frame: CGRect(x: lineXPosition, y: lineYPosition, width: lineWidth, height: lineHeight))
self.addSubview(line)
line.backgroundColor = inActiveColor
}
if errorLabel == nil {
errorLabelYPosition = lineYPosition + 8
errorLabelXPosition = 0
errorLabelWidth = self.frame.width
errorLabelHeight = calculateErrorLabelHeight(text: "")
errorLabel = UILabel(frame: CGRect(x: 0, y: errorLabelYPosition, width: errorLabelWidth, height: errorLabelHeight))
self.addSubview(errorLabel)
errorLabel.numberOfLines = 0
errorLabel.textColor = errorColorFull
errorLabel.text = ""
errorLabel.font = errorLabelFont
sizeToFit()
}
}
func setUpTextField(){
self.rx.controlEvent(.editingDidBegin).subscribe(onNext: { (next) in
if self.text?.isEmpty ?? false {
self.animatePlaceholderUp()
}
}).disposed(by: bag)
self.rx.controlEvent(.editingDidEnd).subscribe(onNext: { (next) in
if self.text?.isEmpty ?? false {
self.animatePlaceholderCenter()
}
}).disposed(by: bag)
}
func setErrorText(_ error: String?, errorAccessibilityValue: String?) {
if let errorText = error {
self.resignFirstResponder()
errorLabelHeight = calculateErrorLabelHeight(text: errorText)
self.errorLabel.frame = CGRect(x: 0, y: errorLabelYPosition, width: errorLabelWidth, height: errorLabelHeight)
self.errorLabel.text = errorText
self.errorLabel.isHidden = false
self.line.backgroundColor = errorColorFull
}else{
self.errorLabel.text = ""
self.errorLabel.isHidden = true
}
errorLabel.accessibilityIdentifier = errorAccessibilityValue ?? "textinput_error"
}
func animatePlaceholderUp(){
UIView.animate(withDuration: animationDuration, animations: {
self.line.frame.size.height = 2
self.line.backgroundColor = self.activeColor
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.minFontSize)
self.placeholderLabel.textColor = self.activeColor
self.placeholderLabel.frame = CGRect(x: 0, y: (self.frame.height/2 + 8) * -1, width: self.frame.width, height: self.frame.height)
self.layoutIfNeeded()
}) { (done) in
}
}
func animatePlaceholderCenter(){
UIView.animate(withDuration: animationDuration, animations: {
self.line.frame.size.height = 1
self.line.backgroundColor = self.inActiveColor
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.maxFontSize)
self.placeholderLabel.textColor = self.inActiveColor
self.placeholderLabel.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
self.layoutIfNeeded()
}) { (done) in
}
}
func calculateErrorLabelHeight(text:String) -> CGFloat{
let font = errorLabelFont
let width = self.frame.width
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
}
我怎样才能解决这个问题?我在stack overflow或google上找不到与我的问题相关的任何内容。如评论中所述:
- 使用约束比使用显式框架要好得多
- 将子视图添加到
将在字段边界之外显示它们,这意味着它们不会影响帧(以及约束)UITextField
- 如果约束设置正确,它们将控制“包含视图”的高度
ui视图的完整示例,其中包含文本字段、占位符标签和错误标签。示例视图控制器包括显示功能的“演示”按钮
我建议您添加此代码并试用。如果它适合你的需要,里面有很多评论,你可以根据自己的喜好调整字体、间距等
或者,它至少应该给你一些如何建立自己的想法
FloatingTextFieldView-UIView子类
class FloatingTextFieldView: UIView, UITextFieldDelegate {
var placeHolderTopConstraint: NSLayoutConstraint!
var placeHolderCenterYConstraint: NSLayoutConstraint!
var placeHolderLeadingConstraint: NSLayoutConstraint!
var lineHeightConstraint: NSLayoutConstraint!
var errorLabelBottomConstraint: NSLayoutConstraint!
var activeColor: UIColor = UIColor.blue
var inActiveColor: UIColor = UIColor(red: 84/255.0, green: 110/255.0, blue: 122/255.0, alpha: 0.8)
var errorColorFull: UIColor = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 1.0)
var animationDuration = 0.35
var maxFontSize: CGFloat = 14
var minFontSize: CGFloat = 11
let errorLabelFont = UIFont(name: "Lato-Regular", size: 12)
let placeholderLabel: UILabel = {
let v = UILabel()
v.text = "Default Placeholder"
v.setContentHuggingPriority(.required, for: .vertical)
return v
}()
let line: UIView = {
let v = UIView()
v.backgroundColor = .lightGray
return v
}()
let errorLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "Default Error"
v.setContentCompressionResistancePriority(.required, for: .vertical)
return v
}()
let textField: UITextField = {
let v = UITextField()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
backgroundColor = .white
[textField, line, placeholderLabel, errorLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
// place holder label gets 2 vertical constraints
// top of view
// centerY to text field
placeHolderTopConstraint = placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0.0)
placeHolderCenterYConstraint = placeholderLabel.centerYAnchor.constraint(equalTo: textField.centerYAnchor, constant: 0.0)
// place holder leading constraint is 16-pts (when centered on text field)
// when animated above text field, we'll change the constant to 0
placeHolderLeadingConstraint = placeholderLabel.leadingAnchor.constraint(equalTo: textField.leadingAnchor, constant: 16.0)
// error label bottom constrained to bottom of view
// will be activated when shown, deactivated when hidden
errorLabelBottomConstraint = errorLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0)
// line height constraint constant changes between 1 and 2 (inactive / active)
lineHeightConstraint = line.heightAnchor.constraint(equalToConstant: 1.0)
NSLayoutConstraint.activate([
// text field top 16-pts from top of view
// leading and trailing = 0
textField.topAnchor.constraint(equalTo: topAnchor, constant: 16.0),
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// text field height = 24
textField.heightAnchor.constraint(equalToConstant: 24.0),
// text field bottom is AT LEAST 4 pts
textField.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -4.0),
// line view top is 2-pts below text field bottom
// leading and trailing = 0
line.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 2.0),
line.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
line.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// error label top is 4-pts from text field bottom
// leading and trailing = 0
errorLabel.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 4.0),
errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
placeHolderCenterYConstraint,
placeHolderLeadingConstraint,
lineHeightConstraint,
])
// I'm not using Rx, so set the delegate
textField.delegate = self
textField.font = UIFont(name: "Lato-Regular", size: maxFontSize)
textField.textColor = .black
placeholderLabel.font = UIFont(name: "Lato-Regular", size: maxFontSize)
placeholderLabel.textColor = inActiveColor
line.backgroundColor = inActiveColor
errorLabel.textColor = errorColorFull
errorLabel.font = errorLabelFont
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.text?.isEmpty ?? false {
self.animatePlaceholderUp()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text?.isEmpty ?? false {
self.animatePlaceholderCenter()
}
}
func animatePlaceholderUp() -> Void {
UIView.animate(withDuration: animationDuration, animations: {
// increase line height
self.lineHeightConstraint.constant = 2.0
// set line to activeColor
self.line.backgroundColor = self.activeColor
// set placeholder label font and color
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.minFontSize)
self.placeholderLabel.textColor = self.activeColor
// deactivate placeholder label CenterY constraint
self.placeHolderCenterYConstraint.isActive = false
// activate placeholder label Top constraint
self.placeHolderTopConstraint.isActive = true
// move placeholder label leading to 0
self.placeHolderLeadingConstraint.constant = 0
self.layoutIfNeeded()
}) { (done) in
}
}
func animatePlaceholderCenter() -> Void {
UIView.animate(withDuration: animationDuration, animations: {
// decrease line height
self.lineHeightConstraint.constant = 1.0
// set line to inactiveColor
self.line.backgroundColor = self.inActiveColor
// set placeholder label font and color
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.maxFontSize)
self.placeholderLabel.textColor = self.inActiveColor
// deactivate placeholder label Top constraint
self.placeHolderTopConstraint.isActive = false
// activate placeholder label CenterY constraint
self.placeHolderCenterYConstraint.isActive = true
// move placeholder label leading to 16
self.placeHolderLeadingConstraint.constant = 16
self.layoutIfNeeded()
}) { (done) in
}
}
func setErrorText(_ error: String?, errorAccessibilityValue: String?, endEditing: Bool) {
if let errorText = error {
UIView.animate(withDuration: 0.05, animations: {
self.errorLabel.text = errorText
self.line.backgroundColor = self.errorColorFull
self.errorLabel.isHidden = false
// activate error label Bottom constraint
self.errorLabelBottomConstraint.isActive = true
}) { (done) in
if endEditing {
self.textField.resignFirstResponder()
}
}
}else{
UIView.animate(withDuration: 0.05, animations: {
self.errorLabel.text = ""
self.line.backgroundColor = self.inActiveColor
self.errorLabel.isHidden = true
// deactivate error label Bottom constraint
self.errorLabelBottomConstraint.isActive = false
}) { (done) in
if endEditing {
self.textField.resignFirstResponder()
}
}
}
errorLabel.accessibilityIdentifier = errorAccessibilityValue ?? "textinput_error"
}
// func to set / clear element background colors
// to make it easy to see the frames
func showHideFrames(show b: Bool) -> Void {
if b {
self.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0)
placeholderLabel.backgroundColor = .cyan
errorLabel.backgroundColor = .green
textField.backgroundColor = .yellow
} else {
self.backgroundColor = .white
[placeholderLabel, errorLabel, textField].forEach {
$0.backgroundColor = .clear
}
}
}
}
DemoFLoatingTextViewController
class DemoFLoatingTextViewController: UIViewController {
// FloatingTextFieldView
let sampleFTF: FloatingTextFieldView = {
let v = FloatingTextFieldView()
return v
}()
// a label to constrain below the FloatingTextFieldView
// so we can see it gets "pushed down"
let demoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "This is a label outside the Floating Text Field. As you will see, it gets \"pushed down\" when the error label is shown."
v.backgroundColor = .brown
v.textColor = .yellow
return v
}()
// buttons to Demo the functionality
let btnA: UIButton = {
let b = UIButton(type: .system)
b.setTitle("End Editing", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnB: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Set Error", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnC: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Clear Error", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnD: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Set & End", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnE: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Clear & End", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnF: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Show Frames", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnG: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Hide Frames", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let errorMessages: [String] = [
"Simple Error",
"This will end up being a Multiline Error message. It is long enough to cause word wrapping."
]
var errorCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// add Demo buttons
let btnStack = UIStackView()
btnStack.axis = .vertical
btnStack.spacing = 6
btnStack.translatesAutoresizingMaskIntoConstraints = false
[[btnA], [btnB, btnC], [btnD, btnE], [btnF, btnG]].forEach { btns in
let sv = UIStackView()
sv.distribution = .fillEqually
sv.spacing = 12
sv.translatesAutoresizingMaskIntoConstraints = false
btns.forEach {
sv.addArrangedSubview($0)
}
btnStack.addArrangedSubview(sv)
}
view.addSubview(btnStack)
// add FloatingTextFieldView and demo label
view.addSubview(sampleFTF)
view.addSubview(demoLabel)
sampleFTF.translatesAutoresizingMaskIntoConstraints = false
demoLabel.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// buttons stack Top = 20, centerX, width = 80% of view width
btnStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
btnStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
btnStack.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
// FloatingTextFieldView Top = 40-pts below buttons stack
sampleFTF.topAnchor.constraint(equalTo: btnStack.bottomAnchor, constant: 40.0),
// FloatingTextFieldView Leading = 60-pts
sampleFTF.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
// FloatingTextFieldView width = 240
sampleFTF.widthAnchor.constraint(equalToConstant: 240.0),
// Note: we are not setting the FloatingTextFieldView Height!
// constrain demo label Top = 8-pts below FloatingTextFieldView bottom
demoLabel.topAnchor.constraint(equalTo: sampleFTF.bottomAnchor, constant: 8.0),
// Leading = FloatingTextFieldView Leading
demoLabel.leadingAnchor.constraint(equalTo: sampleFTF.leadingAnchor),
// Width = 200
demoLabel.widthAnchor.constraint(equalToConstant: 200.0),
])
// add touchUpInside targets for demo buttons
btnA.addTarget(self, action: #selector(endEditing(_:)), for: .touchUpInside)
btnB.addTarget(self, action: #selector(setError(_:)), for: .touchUpInside)
btnC.addTarget(self, action: #selector(clearError(_:)), for: .touchUpInside)
btnD.addTarget(self, action: #selector(setAndEnd(_:)), for: .touchUpInside)
btnE.addTarget(self, action: #selector(clearAndEnd(_:)), for: .touchUpInside)
btnF.addTarget(self, action: #selector(showFrames(_:)), for: .touchUpInside)
btnG.addTarget(self, action: #selector(hideFrames(_:)), for: .touchUpInside)
}
@objc func endEditing(_ sender: Any) -> Void {
sampleFTF.textField.resignFirstResponder()
}
@objc func setError(_ sender: Any) -> Void {
sampleFTF.setErrorText(errorMessages[errorCount % 2], errorAccessibilityValue: "", endEditing: false)
errorCount += 1
}
@objc func clearError(_ sender: Any) -> Void {
sampleFTF.setErrorText(nil, errorAccessibilityValue: "", endEditing: false)
}
@objc func setAndEnd(_ sender: Any) -> Void {
sampleFTF.setErrorText(errorMessages[errorCount % 2], errorAccessibilityValue: "", endEditing: true)
errorCount += 1
}
@objc func clearAndEnd(_ sender: Any) -> Void {
sampleFTF.setErrorText(nil, errorAccessibilityValue: "", endEditing: true)
}
@objc func showFrames(_ sender: Any) -> Void {
sampleFTF.showHideFrames(show: true)
}
@objc func hideFrames(_ sender: Any) -> Void {
sampleFTF.showHideFrames(show: false)
}
}
示例结果:
尝试添加一些视觉效果,以便更容易理解您的意思。。。1) 您确实应该使用自动布局/约束,而不是显式框架。2) 您正在添加子视图(占位符标签
、行
和错误标签
),并将它们定位在文本字段的边界之外。这意味着文本字段框架不会更改。这很可能就是错误标签不会“向下推对象”的原因要获得该功能,您的自定义文本字段必须是UIView
子类,其中包含文本字段、占位符标签、线视图和错误标签。@DonMag感谢您的评论!关于第1点,如果我使用约束来定位组件的对象,我仍然没有用周围的对象。关于第二点,这是我的第一个策略,但将uiView添加到情节提要将迫使我为其指定一个特定高度。这将导致一个新问题,即errorLabel无法扩展超过特定数量的线条。否,您不必为视图指定“特定高度”。这是自动布局和约束的一部分。正确完成后,UIView
(其子视图)的内容可以定义视图的高度。您是否在表格视图单元格中看到多行标签?这正是工作原理。也许您可以帮我,检查我创建的这个xib文件(忘记这个问题).在我用类创建了这个xib文件后,在故事板中我将把每个视图都分配给这个类。在这种情况下,故事板中的视图无法根据内容确定其大小,因为内容在一个单独的xib文件中。我无法将内容添加到故事板,因为我在多个控制器中重复使用它简单而直接的f奥沃德,谢谢!但是你不认为errorLabelBottomConstraint
应该一直处于活动状态吗?@mahdi-试试看会发生什么。