Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios Swift:根据行数将对象推到标签下_Ios_Swift_User Interface_Uitextfield_Material Design - Fatal编程技术网

Ios Swift:根据行数将对象推到标签下

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

我试图使材质设计文本字段的行为与自定义文本字段的行为相同。 我创建了一个从textfield继承的类,一切都很好。唯一的问题是在一个场景中。当我在文本字段下有一个对象时,我在文本字段下添加了错误标签。错误标签可能不止一行。因此它覆盖了textfield下的对象。但是,在材质设计库中,“文本”字段下的对象将根据错误标签的行数自动向下推

这是我的自定义文本字段代码:

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-试试看会发生什么。