How to create TagView: similar to FlexBox's flex-wrap property in Swift
There are several ways to make a tagview, or make a view wrap like the flex-wrap
property of flexbox in css.
One approach is with calculating width value of each item, and move item to the next row if the item past the edge of the container.
Here is the example:
First of all, create UIView
container
private let containerView: UIView = {
let view = UIView()
// use auto layout
view.translatesAutoresizingMaskIntoConstraints = false
// give it a background color so we can see it
view.backgroundColor = .secondarySystemBackground
return view
}()
...
Prepare data in the form of a string array that you will display as tags, you can also get it from your API.
...
private let tagNames: [String] = [
"Swift",
"iOS",
"XCode",
"Objective-C",
"UIKit",
"SwiftUI",
"Core Data",
"Realm",
"Combine",
"RxSwift",
"Clean Architecture",
"MVVM",
"MVC",
"MVP",
"VIPER",
"TCA",
"Algorithm",
"Data Structure"
]
...
Declare a new array in the form of a view, you can create it using a UILabel, in this tutorial I'm using the UIButton array
...
private var tagButtons = [UIButton]()
private let tagHeight:CGFloat = 30
private let tagPadding: CGFloat = 16
private let tagSpacingX: CGFloat = 8
private let tagSpacingY: CGFloat = 8
...
Declare the container height constraint, this will be updated along with the tagview item
...
// container view height will be modified when laying out subviews
private var containerHeightConstraint: NSLayoutConstraint = NSLayoutConstraint()
...
Then in viewDidLoad()
write like this
...
override func viewDidLoad() {
super.viewDidLoad()
// add the container view
view.addSubview(containerView)
// 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
addTagButtons()
}
...
Calculate the frame size and update the container in viewDidLayoutSubviews()
...
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"
displayTagButtons()
}
...
At this point you will definitely get an error, because the addTagButtons()
and displayTagButtons()
functions have not been created, the last step is to make that functions
...
private func addTagButtons() -> Void {
for j in 0..<self.tagNames.count {
// create a new button
let newButton = UIButton(type: .system)
// set its properties (title, colors, corners, etc)
newButton.setTitle(tagNames[j], for: .normal)
newButton.layer.masksToBounds = true
newButton.layer.cornerRadius = 8
newButton.layer.borderWidth = 1
newButton.tintColor = .systemBlue
newButton.configuration = .filled()
// set its frame width and height
newButton.frame.size.width = newButton.intrinsicContentSize.width + tagPadding
newButton.frame.size.height = tagHeight
// add it to the scroll view
containerView.addSubview(newButton)
// append it to tagButtons array
tagButtons.append(newButton)
}
}
private func displayTagButtons() {
let containerWidth = containerView.frame.size.width
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
// for each button in the array
tagButtons.forEach { button in
// if current X + button width will be greater than container view width
// "move to next row"
if currentOriginX + button.frame.width > containerWidth {
currentOriginX = 0
currentOriginY += tagHeight + tagSpacingY
}
// set the btn frame origin
button.frame.origin.x = currentOriginX
button.frame.origin.y = currentOriginY
// increment current X by btn width + spacing
currentOriginX += button.frame.width + tagSpacingX
}
// update container view height
containerHeightConstraint.constant = currentOriginY + tagHeight
}
The result should look like this
Find the entire code on my github here.