有许多不同的方法可以解决这个问题。
一种方法 - 不使用堆栈视图:
- 将标签添加到“容器”视图中
- 以
x = 0 和y = 0 开头
- 遍历标签,计算新的
x 值(标签宽度 + 所需的标签间距)
- 如果新的
x 超出容器边缘,请重置x = 0 并将所需高度添加到y 以“移至下一行”
- 标签布局后,设置容器视图的高度
这是一个简单的例子:
class TagLabelsViewController: UIViewController {
let containerView: UIView = {
let v = UIView()
return v
}()
let tagNames: [String] = [
"First Tag",
"Second",
"Third Tag",
"Fourth",
"The Fifth Tag",
"Sixth",
"Seventh",
"Tag Eight",
"Here are some Letter Tags",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
"Nine",
"Ten",
"Eleven",
"Tag Twelve",
"Tag 13",
"Fourteen",
"Fifteen",
"Sixteen",
"Seventeen",
"Eightteen",
"Nineteen",
"Last Tag",
]
var tagLabels = [UILabel]()
let tagHeight:CGFloat = 30
let tagPadding: CGFloat = 16
let tagSpacingX: CGFloat = 8
let tagSpacingY: CGFloat = 8
// container view height will be modified when laying out subviews
var containerHeightConstraint: NSLayoutConstraint = NSLayoutConstraint()
override func viewDidLoad() {
super.viewDidLoad()
// add the container view
view.addSubview(containerView)
// give it a background color so we can see it
containerView.backgroundColor = .yellow
// use autolayout
containerView.translatesAutoresizingMaskIntoConstraints = false
// initialize height constraint - actual height will be set later
containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 10.0)
// constrain container safe-area top / leading / trailing to view with 20-pts padding
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
containerHeightConstraint,
])
// add the buttons to the scroll view
addTagLabels()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// call this here, after views have been laid-out
// this will also be called when the size changes, such as device rotation,
// so the buttons will "re-layout"
displayTagLabels()
}
func addTagLabels() -> Void {
for j in 0..<self.tagNames.count {
// create a new label
let newLabel = UILabel()
// set its properties (title, colors, corners, etc)
newLabel.text = tagNames[j]
newLabel.textAlignment = .center
newLabel.backgroundColor = UIColor.cyan
newLabel.layer.masksToBounds = true
newLabel.layer.cornerRadius = 8
newLabel.layer.borderColor = UIColor.red.cgColor
newLabel.layer.borderWidth = 1
// set its frame width and height
newLabel.frame.size.width = newLabel.intrinsicContentSize.width + tagPadding
newLabel.frame.size.height = tagHeight
// add it to the scroll view
containerView.addSubview(newLabel)
// append it to tagLabels array
tagLabels.append(newLabel)
}
}
func displayTagLabels() {
let containerWidth = containerView.frame.size.width
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
// for each label in the array
tagLabels.forEach { label in
// if current X + label width will be greater than container view width
// "move to next row"
if currentOriginX + label.frame.width > containerWidth {
currentOriginX = 0
currentOriginY += tagHeight + tagSpacingY
}
// set the btn frame origin
label.frame.origin.x = currentOriginX
label.frame.origin.y = currentOriginY
// increment current X by btn width + spacing
currentOriginX += label.frame.width + tagSpacingX
}
// update container view height
containerHeightConstraint.constant = currentOriginY + tagHeight
}
}
结果:
这很简单,使用代码中的 cmets,您应该能够根据需要对其进行调整。
如果您想要一个“预建”的解决方案,也许有更多的功能,搜索
swift left aligned tags view
想出了很多匹配项。这个(和我无关)看起来很有趣:https://github.com/ElaWorkshop/TagListView
编辑
在表格视图单元格中使用此概念与将其用作视图控制器中的视图没有太大区别。
第一步,让我们创建一个自定义的UIView 子类来处理所有的布局逻辑:
class TagLabelsView: UIView {
var tagNames: [String] = [] {
didSet {
addTagLabels()
}
}
let tagHeight:CGFloat = 30
let tagPadding: CGFloat = 16
let tagSpacingX: CGFloat = 8
let tagSpacingY: CGFloat = 8
var intrinsicHeight: CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
}
func addTagLabels() -> Void {
// if we already have tag labels (or buttons, etc)
// remove any excess (e.g. we had 10 tags, new set is only 7)
while self.subviews.count > tagNames.count {
self.subviews[0].removeFromSuperview()
}
// if we don't have enough labels, create and add as needed
while self.subviews.count < tagNames.count {
// create a new label
let newLabel = UILabel()
// set its properties (title, colors, corners, etc)
newLabel.textAlignment = .center
newLabel.backgroundColor = UIColor.cyan
newLabel.layer.masksToBounds = true
newLabel.layer.cornerRadius = 8
newLabel.layer.borderColor = UIColor.red.cgColor
newLabel.layer.borderWidth = 1
addSubview(newLabel)
}
// now loop through labels and set text and size
for (str, v) in zip(tagNames, self.subviews) {
guard let label = v as? UILabel else {
fatalError("non-UILabel subview found!")
}
label.text = str
label.frame.size.width = label.intrinsicContentSize.width + tagPadding
label.frame.size.height = tagHeight
}
}
func displayTagLabels() {
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
// for each label in the array
self.subviews.forEach { v in
guard let label = v as? UILabel else {
fatalError("non-UILabel subview found!")
}
// if current X + label width will be greater than container view width
// "move to next row"
if currentOriginX + label.frame.width > bounds.width {
currentOriginX = 0
currentOriginY += tagHeight + tagSpacingY
}
// set the btn frame origin
label.frame.origin.x = currentOriginX
label.frame.origin.y = currentOriginY
// increment current X by btn width + spacing
currentOriginX += label.frame.width + tagSpacingX
}
// update intrinsic height
intrinsicHeight = currentOriginY + tagHeight
invalidateIntrinsicContentSize()
}
// allow this view to set its own intrinsic height
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.height = intrinsicHeight
return sz
}
override func layoutSubviews() {
super.layoutSubviews()
displayTagLabels()
}
}
我们可以在单元格中使用它——或者,作为“常规的旧子视图”——像这样:
let tagsView = TagLabelsView()
let tags: [String] = ["One", "Two", "Three", "etc..."]
tagsView.tagNames = tags
这是一个使用我们自定义 TagLabelsView 的完整示例:
class PlainTagLabelsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tagsView = TagLabelsView()
// add the tags view
view.addSubview(tagsView)
// use autolayout
tagsView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain to safe-area top / leading / trailing to view with 20-pts padding
tagsView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
tagsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
tagsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
// some sample "tags" from Stack Overflow
let tags: [String] = [
"asp.net-core",
"asp.net-mvc",
"asp.net",
"azure",
"bash",
"c",
"c#",
"c++",
"class",
"codeigniter",
"cordova",
"css",
"csv",
"dart",
"database",
"dataframe",
]
tagsView.tagNames = tags
// give the tags view a background color so we can see it
tagsView.backgroundColor = .yellow
}
}
要在表格视图单元格中使用它,我们创建一个单元格类,使用我们的TagLabelsView 作为子视图:
class TagsCell: UITableViewCell {
let tagsView: TagLabelsView = {
let v = TagLabelsView()
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// add the container view
contentView.addSubview(tagsView)
// give it a background color so we can see it
tagsView.backgroundColor = .yellow
// use autolayout
tagsView.translatesAutoresizingMaskIntoConstraints = false
// constrain tagsView top / leading / trailing / bottom to
// contentView Layout Margins Guide
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
tagsView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
tagsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tagsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tagsView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
}
func fillData(_ tagNames: [String]) -> Void {
tagsView.tagNames = tagNames
}
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
//force layout of all subviews including RectsView, which
//updates RectsView's intrinsic height, and thus height of a cell
self.setNeedsLayout()
self.layoutIfNeeded()
//now intrinsic height is correct, so we can call super method
return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
}
}
还有一个带有包含多组“标签”的表格视图的示例视图控制器:
class TagLabelsViewController: UIViewController {
var myData: [[String]] = []
let tableView: UITableView = {
let v = UITableView()
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the table view
view.addSubview(tableView)
// use autolayout
tableView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain table view safe-area top / leading / trailing / bottom to view with 20-pts padding
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
tableView.register(TagsCell.self, forCellReuseIdentifier: "c")
tableView.dataSource = self
tableView.delegate = self
// get some sample tag data
myData = SampleTags().samples()
}
}
extension TagLabelsViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! TagsCell
c.fillData(myData[indexPath.row])
return c
}
}
class SampleTags: NSData {
func samples() -> [[String]] {
let tmp: [[String]] = [
[
".htaccess",
".net",
"ajax",
"algorithm",
],
[
"amazon-web-services",
"android-layout",
"android-studio",
"android",
"angular",
"angularjs",
"apache-spark",
],
[
"apache",
"api",
"arrays",
],
[
"asp.net-core",
"asp.net-mvc",
"asp.net",
"azure",
"bash",
"c",
"c#",
"c++",
"class",
"codeigniter",
"cordova",
"css",
"csv",
"dart",
"database",
"dataframe",
],
[
"date",
"datetime",
"dictionary",
"django",
"docker",
],
[
"eclipse",
"email",
"entity-framework",
"excel",
"express",
"facebook",
],
[
"file",
"firebase",
"flutter",
"for-loop",
"forms",
"function",
"git",
"go",
"google-chrome",
"google-maps",
"hibernate",
"html",
"http",
],
[
"image",
"ios",
"iphone",
"java",
"javascript",
"jquery",
"json",
"kotlin",
"laravel",
"linq",
"linux",
],
[
"list",
"loops",
"macos",
"matlab",
"matplotlib",
"maven",
"mongodb",
"multithreading",
"mysql",
"node.js",
],
[
"numpy",
"object",
"objective-c",
"oop",
"opencv",
"oracle",
"pandas",
"performance",
"perl",
"php",
"postgresql",
"powershell",
"python-2.7",
"python-3.x",
"python",
],
[
"qt",
"r",
"react-native",
"reactjs",
"regex",
"rest",
"ruby-on-rails-3",
"ruby-on-rails",
"ruby",
"scala",
"selenium",
"shell",
"sockets",
"sorting",
"spring-boot",
"spring-mvc",
"spring",
"sql-server",
"sql",
],
[
"sqlite",
"string",
"swift",
],
[
"swing",
"symfony",
"tensorflow",
"tsql",
"twitter-bootstrap",
"typescript",
"uitableview",
"unit-testing",
"unity3d",
"validation",
"vb.net",
"vba",
"visual-studio",
"vue.js",
"web-services",
"windows",
"winforms",
"wordpress",
"wpf",
"xaml",
"xcode",
"xml",
],
]
return tmp
}
}
样本输出(iPhone 13 Pro Max):