我怎样才能用这样的图像/图标/视图来偏移合成布局
How can I offset a Compositional Layout with an image / icon / view like this
我正在尝试实现 'tag cloud' 效果,应该看起来像这样 -
每个 Tag Item X
是一个单元格,#
可以是图像、单元格或视图。它只需要这些是哈希标签。
我目前对标签云的尝试是这样的 -
我不知道如何偏移部分项目并将视图或任何类型插入其中 space。
我确实尝试过破解或排序,因为每个单元格都包含图标和标签,然后我在第一个单元格之后隐藏每个单元格上的图标。但是,这不起作用,因为 Tag Item 3
会包裹在图标下方,并且还存在一些重用问题。
请问我怎样才能做到这一点UI?
我相信我可能需要渲染一个嵌套组,第一个单元格中有 icon 和尾随组中的 mu 标签?不过我做不到。
import UIKit
final class CustomCell: UICollectionViewCell {
let label = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.font = .systemFont(ofSize: 16)
label.textColor = .white
label.textAlignment = .center
label.sizeToFit()
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8)
])
}
required init?(coder: NSCoder) {
return nil
}
}
protocol SectionData {
var text: String { get }
}
struct DummyData: SectionData {
let text: String
}
enum SectionType: Int, CaseIterable {
case single
case double
case carousel
case tags
}
struct Section {
let id: Int
let type: SectionType
let title: String?
let subtitle: String?
let data: [SectionData]
}
class ViewController: UIViewController {
private var items: [Section] = [] {
didSet { collectionView.reloadData() }
}
private(set) lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: view.frame, collectionViewLayout: makeLayout())
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .systemBackground
collectionView.dataSource = self
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
items = [
Section(id: 0, type: .single, title: nil, subtitle: nil, data: Array(0...3).map { index in DummyData(text: "List Item \(index)") }),
Section(id: 1, type: .carousel, title: nil, subtitle: nil, data: Array(0...6).map { index in DummyData(text: "Carousel Item \(index)") }),
Section(id: 2, type: .tags, title: nil, subtitle: nil, data: Array(0...15).map { index in DummyData(text: "Tag Item \(index)") })
]
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = items[indexPath.section].data[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = model.text
cell.label.sizeToFit()
cell.backgroundColor = indexPath.item % 2 == 0 ? .darkGray : .lightGray
return cell
}
}
extension ViewController {
func makeLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { [weak self] index, env in
guard let self = self else { return nil }
let section = self.items[index]
switch section.type {
case .single: return self.makeSingleSection()
case .carousel: return self.makeCarouselSection()
case .tags: return self.makeTagSection()
default: return nil
}
}
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 20
layout.configuration = config
return layout
}
func makeSingleSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(12)
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
return layoutSection
}
func makeCarouselSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
layoutItem.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 8)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.83), heightDimension: .estimated(350))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.orthogonalScrollingBehavior = .groupPaging
layoutSection.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 24)
return layoutSection
}
func makeTagSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .absolute(36))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemSize.heightDimension)
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(8)
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
return layoutSection
}
}
我会在前沿使用 NSCollectionLayoutBoundarySupplementaryItem
。
您应该能够使用 absoluteOffset
值正确定位它。
func makeTagSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .absolute(36))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemSize.heightDimension)
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(8)
layoutGroup.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .fixed(44), top: nil, trailing: nil, bottom: nil)
let leftSize = NSCollectionLayoutSize(widthDimension: .absolute(36), heightDimension: .absolute(36))
let left = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: leftSize, elementKind: ViewController.leadingKind, alignment: .topLeading, absoluteOffset: .init(x: 0, y: 36))
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
layoutSection.boundarySupplementaryItems = [left]
return layoutSection
}
您可以简单地创建一个 UICollectionReusableView
来代表您的图标。
我正在尝试实现 'tag cloud' 效果,应该看起来像这样 -
每个 Tag Item X
是一个单元格,#
可以是图像、单元格或视图。它只需要这些是哈希标签。
我目前对标签云的尝试是这样的 -
我不知道如何偏移部分项目并将视图或任何类型插入其中 space。
我确实尝试过破解或排序,因为每个单元格都包含图标和标签,然后我在第一个单元格之后隐藏每个单元格上的图标。但是,这不起作用,因为 Tag Item 3
会包裹在图标下方,并且还存在一些重用问题。
请问我怎样才能做到这一点UI?
我相信我可能需要渲染一个嵌套组,第一个单元格中有 icon 和尾随组中的 mu 标签?不过我做不到。
import UIKit
final class CustomCell: UICollectionViewCell {
let label = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.font = .systemFont(ofSize: 16)
label.textColor = .white
label.textAlignment = .center
label.sizeToFit()
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8)
])
}
required init?(coder: NSCoder) {
return nil
}
}
protocol SectionData {
var text: String { get }
}
struct DummyData: SectionData {
let text: String
}
enum SectionType: Int, CaseIterable {
case single
case double
case carousel
case tags
}
struct Section {
let id: Int
let type: SectionType
let title: String?
let subtitle: String?
let data: [SectionData]
}
class ViewController: UIViewController {
private var items: [Section] = [] {
didSet { collectionView.reloadData() }
}
private(set) lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: view.frame, collectionViewLayout: makeLayout())
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .systemBackground
collectionView.dataSource = self
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
items = [
Section(id: 0, type: .single, title: nil, subtitle: nil, data: Array(0...3).map { index in DummyData(text: "List Item \(index)") }),
Section(id: 1, type: .carousel, title: nil, subtitle: nil, data: Array(0...6).map { index in DummyData(text: "Carousel Item \(index)") }),
Section(id: 2, type: .tags, title: nil, subtitle: nil, data: Array(0...15).map { index in DummyData(text: "Tag Item \(index)") })
]
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = items[indexPath.section].data[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = model.text
cell.label.sizeToFit()
cell.backgroundColor = indexPath.item % 2 == 0 ? .darkGray : .lightGray
return cell
}
}
extension ViewController {
func makeLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { [weak self] index, env in
guard let self = self else { return nil }
let section = self.items[index]
switch section.type {
case .single: return self.makeSingleSection()
case .carousel: return self.makeCarouselSection()
case .tags: return self.makeTagSection()
default: return nil
}
}
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 20
layout.configuration = config
return layout
}
func makeSingleSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(12)
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
return layoutSection
}
func makeCarouselSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
layoutItem.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 8)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.83), heightDimension: .estimated(350))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.orthogonalScrollingBehavior = .groupPaging
layoutSection.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 24)
return layoutSection
}
func makeTagSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .absolute(36))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemSize.heightDimension)
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(8)
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
return layoutSection
}
}
我会在前沿使用 NSCollectionLayoutBoundarySupplementaryItem
。
您应该能够使用 absoluteOffset
值正确定位它。
func makeTagSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .absolute(36))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemSize.heightDimension)
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(8)
layoutGroup.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .fixed(44), top: nil, trailing: nil, bottom: nil)
let leftSize = NSCollectionLayoutSize(widthDimension: .absolute(36), heightDimension: .absolute(36))
let left = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: leftSize, elementKind: ViewController.leadingKind, alignment: .topLeading, absoluteOffset: .init(x: 0, y: 36))
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
layoutSection.boundarySupplementaryItems = [left]
return layoutSection
}
您可以简单地创建一个 UICollectionReusableView
来代表您的图标。