iOS - Add vertical line programatically inside a stack view

You can try the following.

  1. First of all take a UIView and apply the same constraints of UIStackView to this UIView.
  2. Make the Background color of this UIView to Black (The color of the lines)
  3. Now take a UIStackView and add it as a child of above UIView.
  4. Add constraints of the UIStackView i.e. bind it to all the edges of parent UIView.
  5. Now make the bakckground color of UIStackView to Clear Color.
  6. Set the spacing of UIStackView to 1 or 2 (the width of lines)
  7. Now add the 3 labels into stackview.
  8. Make sure the labels have background color to White Color and Text Color to Black Color.

Thus you'll achieve the required scene. See these pictures for reference.

enter image description here enter image description here


You can't use the same view in two places, so you'll need to create two separate vertical line views. You need to configure each vertical line view like this:

  • Set its background color.
  • Constrain its width to 1 (so you get a line, not a rectangle).
  • Constrain its height (so it doesn't get stretched to the full height of the stack view).

So add the labels one at a time to the stack view, and do something like this before adding each label to the stack view:

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
}

Note that you do not want the vertical lines to be the same width as the labels, so you must not set the distribution property of the stack view to fillEqually. Instead, if you want all the labels to have equal width, you must create width constraints between the labels yourself. For example, after adding each new label, do this:

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

Result:

result

Full playground code (updated to Swift 4.1 by Federico Zanetello):

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))