【问题标题】:Label and TextView overflows Scroll View horizontallyLabel 和 TextView 水平溢出 Scroll View
【发布时间】:2021-03-20 19:29:33
【问题描述】:

我有一个滚动视图,其中有一个堆栈视图。在堆栈视图中,我安排了 UITextView 或 UILabel 元素的子视图。 全部以编程方式完成,无需情节提要。

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

如果它很重要,则在 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 分别位于滚动视图的上方和下方。

【问题讨论】:

    标签: ios swift uiscrollview


    【解决方案1】:

    我可以解决它,也许不是最好的解决方案,所以我将问题暂时搁置一会,以获得更好的解决方案。

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

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

    【讨论】:

      【解决方案2】:

      第一个注意事项:发布代码时,请发布一些实际代码。您的代码指的是scrollViewrecipeScrollView,我认为它们是相同的滚动视图。另外,请尝试发布完整信息 - 您的代码还引用了 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,而不是滚动视图本身。

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

      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: [])
          }
          
      }
      

      【讨论】:

      • 谢谢!它有很大帮助,根据您的建议更新了我的代码并起到了魅力。第一个注意事项:您是对的,对此感到抱歉。我想减少代码,认为它会更容易阅读......第二,第三注:感谢您的提示!我以前不知道左/右前/尾之间的区别......现在阅读它。第 4 点:它有效,但我可以请你更好地解释一下这个 contantLayoutGuide 和 frameLayoutGuide 吗?我不明白为什么需要它们以及它们的作用。
      • @zdtorok - 希望这个答案有助于解释 contentLayoutGuideframeLayoutGuide: stackoverflow.com/a/64560447/6257435