iOS - 在堆栈视图中以编程方式添加垂直线

时间:2017-05-21 15:00:10

标签: ios uiview swift3 uistackview

我正在尝试以编程方式在堆栈视图内的标签之间添加垂直线。

所需的完成将是这样的图像:

stackview

我可以添加标签,所有标签都有所需的间距;我可以添加水平线,但我无法弄清楚如何在中间添加这些分隔符垂直线。

我想这样做:

let stackView = UIStackView(arrangedSubviews: [label1, verticalLine, label2, verticalLine, label3])

任何提示?

由于

12 个答案:

答案 0 :(得分:19)

您无法在两个地方使用相同的视图,因此您需要创建两个单独的垂直线视图。您需要像这样配置每个垂直线视图:

  • 设置背景颜色。
  • 将宽度限制为1(因此您得到一条线,而不是一个矩形)。
  • 限制其高度(因此它不会被拉伸到堆栈视图的整个高度)。

因此,一次一个地添加标签到堆栈视图,并在将每个标签添加到堆栈视图之前执行类似的操作:

if stackView.arrangedSubviews.count > 0 {
    let separator = UIView()
    separator.widthAnchor.constraint(equalToConstant: 1).isActive = true
    separator.backgroundColor = .black
    stackView.addArrangedSubview(separator)
    separator.heightAnchor.constraint(equalTo: stackView.heightAnchor, multiplier: 0.6).isActive = true
}

请注意,您希望垂直线与标签的宽度相同,因此您必须设置堆栈的distribution属性查看fillEqually。相反,如果您希望所有标签具有相等的宽度,则必须自己在标签之间创建宽度约束。例如,添加每个新标签后,请执行以下操作:

if let firstLabel = stackView.arrangedSubviews.first as? UILabel {
    label.widthAnchor.constraint(equalTo: firstLabel.widthAnchor).isActive = true
}

结果:

result

完整的游乐场代码(由Federico Zanetello更新为Swift 4.1):

import UIKit
import PlaygroundSupport

extension UIFont {
  var withSmallCaps: UIFont {
    let feature: [UIFontDescriptor.FeatureKey: Any] = [
      UIFontDescriptor.FeatureKey.featureIdentifier: kLowerCaseType,
      UIFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseSmallCapsSelector]
    let attributes: [UIFontDescriptor.AttributeName: Any] = [UIFontDescriptor.AttributeName.featureSettings: [feature]]
    let descriptor = self.fontDescriptor.addingAttributes(attributes)
    return UIFont(descriptor: descriptor, size: pointSize)
  }
}

let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 44))
rootView.backgroundColor = .white
PlaygroundPage.current.liveView = rootView

let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.frame = rootView.bounds
rootView.addSubview(stackView)

typealias Item = (name: String, value: Int)
let items: [Item] = [
  Item(name: "posts", value: 135),
  Item(name: "followers", value: 6347),
  Item(name: "following", value: 328),
]

let valueStyle: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 12).withSmallCaps]
let nameStyle: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12).withSmallCaps,
                                NSAttributedStringKey.foregroundColor: UIColor.darkGray]
let valueFormatter = NumberFormatter()
valueFormatter.numberStyle = .decimal

for item in items {
  if stackView.arrangedSubviews.count > 0 {
    let separator = UIView()
    separator.widthAnchor.constraint(equalToConstant: 1).isActive = true
    separator.backgroundColor = .black
    stackView.addArrangedSubview(separator)
    separator.heightAnchor.constraint(equalTo: stackView.heightAnchor, multiplier: 0.4).isActive = true
  }

  let richText = NSMutableAttributedString()
  let valueString = valueFormatter.string(for: item.value)!
  richText.append(NSAttributedString(string: valueString, attributes: valueStyle))
  richText.append(NSAttributedString(string: "\n" + item.name, attributes: nameStyle))
  let label = UILabel()
  label.attributedText = richText
  label.textAlignment = .center
  label.numberOfLines = 0
  stackView.addArrangedSubview(label)

  if let firstLabel = stackView.arrangedSubviews.first as? UILabel {
    label.widthAnchor.constraint(equalTo: firstLabel.widthAnchor).isActive = true
  }
}

UIGraphicsBeginImageContextWithOptions(rootView.bounds.size, true, 1)
rootView.drawHierarchy(in: rootView.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
let png = UIImagePNGRepresentation(image)!
let path = NSTemporaryDirectory() + "/image.png"
Swift.print(path)
try! png.write(to: URL(fileURLWithPath: path))

答案 1 :(得分:16)

您可以尝试以下操作。

  1. 首先采用UIView并将相同的UIStackView约束应用于此UIView。
  2. 将此UIView的背景颜色设为黑色(线条颜色)
  3. 现在拿一个UIStackView并将其添加为UIView上面的孩子。
  4. 添加UIStackView的约束,即将其绑定到父UIView的所有边缘。
  5. 现在将UIStackView的bakckground颜色设置为Clear Color。
  6. 将UIStackView的间距设置为1或2(线宽)
  7. 现在将3个标签添加到stackview中。
  8. 确保标签的背景颜色为白色,文字颜色为黑色。
  9. 因此,您将实现所需的场景。请参阅这些图片以供参考。

    enter image description here enter image description here

答案 2 :(得分:6)

这是一个简单的扩展,用于在每行之间添加分隔符(注意!行,而不是所要求的列!对于这种情况也很容易修改)。与接受的答案基本相同,但格式可重用。

通过致电例如

yourStackViewObjectInstance.addHorizontalSeparators(color : .black)

扩展名:

extension UIStackView {
    func addHorizontalSeparators(color : UIColor) {
        var i = self.arrangedSubviews.count
        while i >= 0 {
            let separator = createSeparator(color: color)
            insertArrangedSubview(separator, at: i)
            separator.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1).isActive = true
            i -= 1
        }
    }

    private func createSeparator(color : UIColor) -> UIView {
        let separator = UIView()
        separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
        separator.backgroundColor = color
        return separator
    }
}

答案 3 :(得分:1)

@GOR答案扩展仅适用于垂直线且仅适用于中心

Stackview设置:设置每个子视图和父Stackview的宽度约束

这是一个简单的扩展,用于在每行之间添加垂直分隔符。

func addVerticalSeparators(color : UIColor) {
    var i = self.arrangedSubviews.count
    while i > 1 {
        let separator = verticalCreateSeparator(color: color)
        insertArrangedSubview(separator, at: i-1)   // (i-1) for centers only
        separator.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true
        i -= 1
    }
}

private func verticalCreateSeparator(color : UIColor) -> UIView {
    let separator = UIView()
    separator.widthAnchor.constraint(equalToConstant: 1).isActive = true
    separator.backgroundColor = color
    return separator
} 

答案 4 :(得分:0)

这是一个更加灵活的UIStackView子类,它支持任意添加已安排的子视图,并且适合那些需要在其UIStackView和子视图上有清晰背景才能放在{{1}上的子类},如下图所示。

UIVisualEffectView

结果?

SeparatorStackView on top of a UIVisualEffectView

答案 5 :(得分:0)

如果垂直线字符“ |”适合您想要的外观,则可以在要使用分隔线的堆栈视图中添加标签。然后使用:

myStackView.distribution = .equalSpacing

您还可以在Interface Builder中更改堆栈视图分布。

答案 6 :(得分:0)

@ mark-bourke的答案仅适用于单个分隔符。我为多个分隔符修复了invalidateSeparators()方法。我尚未使用水平堆栈视图对其进行测试,但它适用于垂直视图:

  private func invalidateSeparators() {
    guard arrangedSubviews.count > 1 else {
      separators.forEach({$0.removeFromSuperview()})
      separators.removeAll()
      return
    }

    if separators.count > arrangedSubviews.count {
      separators.removeLast(separators.count - arrangedSubviews.count)
    } else if separators.count < arrangedSubviews.count {
      for _ in 0..<(arrangedSubviews.count - separators.count - 1) {
        separators.append(UIView())
      }
    }

    separators.forEach({$0.backgroundColor = self.separatorColor; self.addSubview($0)})

    for (index, subview) in arrangedSubviews.enumerated() where arrangedSubviews.count >= index + 2 {
      let nextSubview = arrangedSubviews[index + 1]
      let separator = separators[index]

      let origin: CGPoint
      let size: CGSize

      if axis == .horizontal {
        let originX = subview.frame.maxX + (nextSubview.frame.minX - subview.frame.maxX) / 2.0 + separatorInsets.left - separatorInsets.right
        origin = CGPoint(x: originX, y: separatorInsets.top)
        let height = frame.height - separatorInsets.bottom - separatorInsets.top
        size = CGSize(width: separatorWidth, height: height)
      } else {
        let originY = subview.frame.maxY + (nextSubview.frame.minY - subview.frame.maxY) / 2.0 + separatorInsets.top - separatorInsets.bottom
        origin = CGPoint(x: separatorInsets.left, y: originY)
        let width = frame.width - separatorInsets.left - separatorInsets.right
        size = CGSize(width: width, height: separatorWidth)
      }

      separator.frame = CGRect(origin: origin, size: size)
      separator.isHidden = nextSubview.isHidden
    }
  }

答案 7 :(得分:0)

很好的可可豆荚。 它使用Swizzeling,编码良好。

https://github.com/barisatamer/StackViewSeparator

enter image description here

答案 8 :(得分:0)

我赞成@ FrankByte.com的回答,因为他帮助我解决了这个问题,这与OP想要做的非常相似:


extension UIStackView {
    func addVerticalSeparators(color : UIColor, multiplier: CGFloat = 0.5) {
        var i = self.arrangedSubviews.count - 1
        while i > 0 {
            let separator = createSeparator(color: color)
            insertArrangedSubview(separator, at: i)
            separator.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: multiplier).isActive = true
            i -= 1
        }
    }

    private func createSeparator(color: UIColor) -> UIView {
        let separator = UIView()
        separator.widthAnchor.constraint(equalToConstant: 1).isActive = true
        separator.backgroundColor = color
        return separator
    }
}

答案 9 :(得分:0)

您可以使用界面生成器来执行此操作。将父UIStackview的分布设置为等距居中,并在父UIStackView的开头和结尾添加两个宽度为0的UIView。然后在中间添加一个宽度为1的UIView,并设置UIView的背景颜色。

enter image description here enter image description here

答案 10 :(得分:0)

@OwlOCR 解决方案的另一个版本,如果我们不想在开头和结尾使用分隔符。

Instance IDs: ['i-0b956a2987c686dbd', ' i-0f6b1e1cdcabd2594', ' i-0307f123c3c9e6695', ' i-0425545775a09443d', ' i-00e48354b77e70484']

答案 11 :(得分:0)

我的解决方案很简单。您可以在 UIStackView 中的轴属性上使用 switch 语句来检查当前正在使用哪个轴,而不是为垂直和水平轴创建方法。

要添加分隔符,只需添加此扩展名并传递分隔符应放置的位置和颜色。

extension UIStackView {

func addSeparators(at positions: [Int], color: UIColor) {
    for position in positions {
        let separator = UIView()
        separator.backgroundColor = color
        
        insertArrangedSubview(separator, at: position)
        switch self.axis {
        case .horizontal:
            separator.widthAnchor.constraint(equalToConstant: 1).isActive = true
            separator.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true
        case .vertical:
            separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
            separator.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1).isActive = true
        @unknown default:
            fatalError("Unknown UIStackView axis value.")
        }
    }
}

}

示例用例:

stackView.addSeparators(at: [2], color: .black)