スタックビュー内のラベル間に垂直線をすべてプログラムで追加しようとしています。
希望する仕上げは次の画像のようになります。
すべて希望する間隔でラベルを追加できます。水平線を追加することはできますが、それらの間に垂直線を追加する方法がわかりません。
私はそれを次のようにしたいと思います:
let stackView = UIStackView(arrangedSubviews: [label1, verticalLine, label2, verticalLine, label3])
ヒントはありますか?
ありがとう
2つの場所で同じビューを使用することはできないため、2つの別々の垂直線ビューを作成する必要があります。次のように各垂直線ビューを構成する必要があります。
したがって、ラベルを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
}
notを実行すると、垂直線をラベルと同じ幅にする必要があるため、notスタックビューのdistribution
プロパティをfillEqually
に設定します。代わりに、すべてのラベルの幅を等しくしたい場合は、ラベル間に幅の制約を自分で作成する必要があります。たとえば、新しいラベルをそれぞれ追加した後、次のようにします。
if let firstLabel = stackView.arrangedSubviews.first as? UILabel {
label.widthAnchor.constraint(equalTo: firstLabel.widthAnchor).isActive = true
}
結果:
完全な遊び場コード(Swift 4.1に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))
以下を試すことができます。
したがって、必要なシーンを実現できます。参考のためにこれらの写真を参照してください。
以下は、各行の間にセパレーターを追加するための単純な拡張機能です(注!行ではなく、列ではありません!その場合も簡単に変更できます)。基本的に受け入れられた回答と同じですが、再利用可能な形式です。
呼び出して使用します。
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
}
}
垂直線のみおよび中心のみの@GOR回答拡張
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
}
縦線文字「|」が希望の外観で機能する場合は、セパレーター線を配置するスタックビューにラベルを追加できます。次に使用します:
myStackView.distribution = .equalSpacing
Interface Builderでスタックビューの分布を変更することもできます。
以下は、より柔軟なUIStackView
サブクラスです。これは、配置されたサブビューの任意の追加をサポートし、以下の図のように、UIStackView
およびサブビューの明確な背景がUIVisualEffectView
の上に配置される必要がある場合に適しています。
_import UIKit
@IBDesignable class SeparatorStackView: UIStackView {
@IBInspectable var separatorColor: UIColor? = .black {
didSet {
invalidateSeparators()
}
}
@IBInspectable var separatorWidth: CGFloat = 0.5 {
didSet {
invalidateSeparators()
}
}
@IBInspectable private var separatorTopPadding: CGFloat = 0 {
didSet {
separatorInsets.top = separatorTopPadding
}
}
@IBInspectable private var separatorBottomPadding: CGFloat = 0 {
didSet {
separatorInsets.bottom = separatorBottomPadding
}
}
@IBInspectable private var separatorLeftPadding: CGFloat = 0 {
didSet {
separatorInsets.left = separatorLeftPadding
}
}
@IBInspectable private var separatorRightPadding: CGFloat = 0 {
didSet {
separatorInsets.right = separatorRightPadding
}
}
var separatorInsets: UIEdgeInsets = .zero {
didSet {
invalidateSeparators()
}
}
private var separators: [UIView] = []
override func layoutSubviews() {
super.layoutSubviews()
invalidateSeparators()
}
override func awakeFromNib() {
super.awakeFromNib()
invalidateSeparators()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
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 {
separators += Array<UIView>(repeating: UIView(), count: arrangedSubviews.count - separators.count)
}
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 = (nextSubview.frame.maxX - subview.frame.minX)/2 + 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 = (nextSubview.frame.maxY - subview.frame.minY)/2 + 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)
}
}
}
_
結果?
@ 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
}
}