Label 和 TextView 横向溢出 Scroll View

Label and TextView overflows Scroll View horizontally

我有一个滚动视图,其中有一个堆栈视图。在堆栈视图中,我安排了 UITextView 或 UILabel 元素的子视图。 一切都以编程方式完成,没有故事板。

滚动视图出现,我可以很好地滚动它。但不幸的是,它不仅垂直滚动(从上到下)而且水平滚动(向右,屏幕外),我不想这样做(这就是我在 UILabel 上设置 numberOfLines 的原因,试图设置相等的宽度到滚动和堆栈视图,因为堆栈视图的 left/right 属性已连接到视图)。

如果它很重要,则在 viewDidLoad 中或稍后触摸按钮时调用此函数。

    scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(scrollView)
    let leftConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
    let rightConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
    let topConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: selectedTabIndicator, attribute: .bottom, multiplier: 1, constant: 10)
    let bottomConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: editButton, attribute: .top, multiplier: 1, constant: 0)
    view.addConstraints([leftConstraintScroll, rightConstraintScroll, topConstraintScroll, bottomConstraintScroll])

    stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .vertical
    stackView.spacing = 10
    stackView.isLayoutMarginsRelativeArrangement = true
    stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10)

    // Several elements are added like this (UITextView):
    let textView = UITextView()
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.delegate = self
    textView.isScrollEnabled = false
    textView.font = UIFont.systemFont(ofSize: 15)
    textView.backgroundColor = Constants.COLOR_P
    textView.textColor = .black
    textView.text = "XXX"
    stackView.addArrangedSubview(textView)

    // Or UILabel:
    var label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.numberOfLines = 0
    label.textAlignment = .justified
    label.textColor = .black
    label.font = UIFont.systemFont(ofSize: 15)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .justified
    paragraphStyle.hyphenationFactor = 1.0
    paragraphStyle.firstLineHeadIndent = 0
    paragraphStyle.headIndent = 15
    let hyphenAttribute = [NSAttributedString.Key.paragraphStyle: paragraphStyle]
    let attributedString = NSMutableAttributedString(string: "XXXXX", attributes: hyphenAttribute)
    label.attributedText = attributedString
    stackView.addArrangedSubview(label)
    
    scrollView.addSubview(stackView)
    let leftConstraint = NSLayoutConstraint(item: stackView, attribute: .left, relatedBy: .equal, toItem: scrollView, attribute: .left, multiplier: 1, constant: 0)
    let rightConstraint = NSLayoutConstraint(item: stackView, attribute: .right, relatedBy: .equal, toItem: scrollView, attribute: .right, multiplier: 1, constant: 0)
    let topConstraint = NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1, constant: 0)
    let bottomConstraint = NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1, constant: 0)
    scrollView.addConstraints([leftConstraint, rightConstraint, topConstraint, bottomConstraint, bottomConstraint])

注意:selectedTabIndicator 和 editButton 分别位于滚动视图的上方和下方。

我可以解决它,也许不是最好的解决方案,所以我暂时把这个问题悬而未决,以获得更好的解决方案。

基本上,我在创建它们时为每个 UILabel 和 UITextView 提供了 widthAnchor 约束,然后再将它们添加到堆栈视图。

label.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true

首先注意:post输入代码时,post一些实际代码。您的代码指的是 scrollViewrecipeScrollView,我认为它们是相同的滚动视图。另外,请尝试 post 完整信息 - 您的代码还引用了 selectedTabIndicatoreditButton,这两个都没有在您的问题中被识别或描述。

第二个注意事项:开始使用更现代的约束语法。例如:

NSLayoutConstraint.activate([
    recipeScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
    recipeScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
    recipeScrollView.topAnchor.constraint(equalTo: selectedTabIndicator.bottomAnchor, constant: 10.0),
    recipeScrollView.bottomAnchor.constraint(equalTo: editButton.topAnchor, constant: 0.0),
])

更易于使用(和阅读)
let leftConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
let rightConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
let topConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .top, relatedBy: .equal, toItem: selectedTabIndicator, attribute: .bottom, multiplier: 1, constant: 10)
let bottomConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .bottom, relatedBy: .equal, toItem: editButton, attribute: .top, multiplier: 1, constant: 0)
view.addConstraints([leftConstraintScroll, rightConstraintScroll, topConstraintScroll, bottomConstraintScroll])

第三个注意事项:尊重安全区...因此您的主要约束应该是:

recipeScrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0)

等等。

第四点注意:将滚动视图的内容限制在 .contentLayoutGuide,而不是滚动视图本身。

要解决您的“水平滚动”问题,请设置堆栈视图相对于滚动视图的 .frameLayoutGuide 的宽度,而不是设置 labeltextView 宽度:

stackView.widthAnchor.constraint(equalTo: recipeScrollView.frameLayoutGuide.widthAnchor, constant: 0.0)

这是您的代码,使用这些提示进行了编辑。我在顶部附近放了一个蓝色视图作为 selectedTabIndicator,在底部附近放了一个蓝色按钮作为 editButton:

class AnotherScrollViewController: UIViewController, UITextViewDelegate {
    
    var recipeScrollView: UIScrollView!
    var stackView: UIStackView!
    var textView: UITextView!

    var selectedTabIndicator: UIView!
    var editButton: UIButton!
    
    var editButtonBottom: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        selectedTabIndicator = UIView()
        selectedTabIndicator.backgroundColor = .blue
        selectedTabIndicator.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(selectedTabIndicator)
        
        editButton = UIButton()
        editButton.backgroundColor = .blue
        editButton.setTitle("Edit", for: [])
        editButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(editButton)
        
        recipeScrollView = UIScrollView()
        recipeScrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(recipeScrollView)
        
        let g = view.safeAreaLayoutGuide
        
        editButtonBottom = editButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0)

        NSLayoutConstraint.activate([
            
            selectedTabIndicator.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            selectedTabIndicator.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
            selectedTabIndicator.widthAnchor.constraint(equalToConstant: 200.0),
            selectedTabIndicator.heightAnchor.constraint(equalToConstant: 4.0),
            
            //editButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 4.0),
            editButtonBottom,
            editButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            recipeScrollView.topAnchor.constraint(equalTo: selectedTabIndicator.bottomAnchor, constant: 10.0),
            recipeScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            recipeScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            recipeScrollView.bottomAnchor.constraint(equalTo: editButton.topAnchor, constant: 0.0),

        ])

        stackView = UIStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.spacing = 10
        stackView.isLayoutMarginsRelativeArrangement = true
        stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10)
        
        // Several elements are added like this (UITextView):
        textView = UITextView()
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.delegate = self
        textView.isScrollEnabled = false
        textView.font = UIFont.systemFont(ofSize: 15)
        textView.backgroundColor = .cyan // Constants.COLOR_P
        textView.textColor = .black
        textView.text = "XXX"
        stackView.addArrangedSubview(textView)
        
        // Or UILabel:
        var label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        label.textAlignment = .justified
        label.backgroundColor = .green  // so we can easily see the label frame
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 15)
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .justified
        paragraphStyle.hyphenationFactor = 1.0
        paragraphStyle.firstLineHeadIndent = 0
        paragraphStyle.headIndent = 15
        let hyphenAttribute = [NSAttributedString.Key.paragraphStyle: paragraphStyle]
        let labelString = "This is the string for the label. It will wrap if it is too long to fit in the allocated width."
        //let attributedString = NSMutableAttributedString(string: "XXXXX", attributes: hyphenAttribute)
        let attributedString = NSMutableAttributedString(string: labelString, attributes: hyphenAttribute)
        label.attributedText = attributedString
        stackView.addArrangedSubview(label)

        recipeScrollView.addSubview(stackView)

        let contentG = recipeScrollView.contentLayoutGuide
        let frameG = recipeScrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            stackView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),

            stackView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
            
        ])

        recipeScrollView.backgroundColor = .red
        
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
        
        editButton.addTarget(self, action: #selector(self.editButtonTapped), for: .touchUpInside)
    }

    @objc func editButtonTapped() -> Void {
        if textView.isFirstResponder {
            textView.resignFirstResponder()
        } else {
            textView.becomeFirstResponder()
        }
    }
    
    @objc func adjustForKeyboard(notification: Notification) {
        guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
        
        let keyboardScreenEndFrame = keyboardValue.cgRectValue
        let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
        print(keyboardViewEndFrame.height)
        var c: CGFloat = -4.0
        if notification.name != UIResponder.keyboardWillHideNotification {
            c -= (keyboardViewEndFrame.height - view.safeAreaInsets.bottom)
        }
        
        editButtonBottom.constant = c
        
        editButton.setTitle(c == -4 ? "Edit" : "Done", for: [])
    }
    
}