- Published on
๐ฑ iOS - CompositionalLayout.01
- Authors
- Name
- ์ด์ฐฝ์ค
Compositional Layout
๋์ ๋ฐ์์ฑ์ ๊ฐ๊ณ ์ ์ฐํ UI ์กฐ์ ์ด ๊ฐ๋ฅํ item๋ค์ ์กฐํฉํ์ฌ ์ฌ์ฉํ๋ ๋ ์ด์์์ ๋๋ค.
์ ํ์ด Compositional Layout์ ์๊ฐํ๋ฉด์ ๋ํ์ ์ผ๋ก ์์๋ฅผ ๋ ์ฑ์ด App Store์ ์ฌ์ง ์ฑ์ ๋๋ค.
์ฌ๋ฌ๊ฐ์ง ํํ์ ๋ ์ด์์๋ค์ด ์ค์ง์ด ์์ง๋ง, ์ด ํ๋ฉด์ ๋จ ํ๋์ CollectionView
๋ก ์ด๋ฃจ์ด์ ธ์๋ค๊ณ ํฉ๋๋ค.
๊ธฐ์กด์ FlowLayout
์ ์ฌ์ฉํ๋ค๋ฉด ๊ฐ๊ธฐ ๋ค๋ฅธ ๋ ์ด์์์ ๋ฐ๋ผ ์ฌ๋ฌ๊ฐ์ CollectionView
๋ฅผ ์ฌ์ฉํ์ด์ผ ํ์ง๋ง CompositionalLayout
์ ์ฌ์ฉํ๋ฉด ํ๋์ CollectionView
๋ก ์ฒ๋ฆฌํ ์ ์๋ค๋ ๊ฒ์ด์ฃ .
๊ทธ๋ฌ๋ฉด์๋ ์ด์ ๋ณด๋ค ์ฌ์ด API๋ก ๊ตฌํ์ด ๊ฐ๋ฅํ๊ณ , ๋น ๋ฅด๊ณ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ์ ๋ ์ฐ์ํ๋ค๊ณ ํฉ๋๋ค.
๊ธฐ๋ณธ ๊ฐ๋
CompositionalLayout
์ ์ธ ๊ฐ์ง์ ํต์ฌ ์์์ ์ง์คํ๋ฉด ๋ฉ๋๋ค.
๊ฐ์ฅ ์์ ๋จ์์ธ Item
์ด ๋ชจ์ฌ์ Group
์ด ๋๊ณ , ํ๋์ ์ค(row)๋ฅผ ํ์ฑํฉ๋๋ค.
์ด Group
๋ค์ ๋ค์ ํ๋ฒ ๋ชจ์ฌ์ Section
์ ์ด๋ฃน๋๋ค. ์ด๋ ๊ฒ ๋ง๋ค์ด์ง ๋ชจ๋ Section
์ Layout
์ด ๋ด๊ณ ์๋ ๊ฒ์ด์ฃ .
Size
CompositionalLayout
์ด ๊ฐ๊ณ ์๋ ์ด ๋ชจ๋ ์์๋ค์ ๊ฐ๊ฐ์ size
๋ฅผ ๊ฐ๊ณ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ size
๋ ๋ชจ๋๊ฐ ์๊ณ ์๋ฏ์ด width
์ height
, ๋ ๊ฐ์ง ์์ฑ์ผ๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
class NSCollectionLayoutSize {
init(
widthDimension: NSCollectionLayoutDimension,
heightDimension: NSCollectionLayoutDimension
)
}
ํ์ง๋ง ์ฃผ์ํ ์ ์ ์ด width
์ height
๋ ์ค์นผ๋ผ ๊ฐ์ด ์๋๋๋ค.
๊ทธ๋์ Float
์ ๊ฐ์ ํ์
์ด ์๋๋ผ NSCollectionLayoutDimension
ํ์
์ ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ์ทจํฉ๋๋ค.
class NSCollectionLayoutDimension {
class func fractionalWidth(_ fractionalWidth: CGFloat) -> Self
class func fractionalHeight(_ fractionalHeight: CGFloat) -> Self
class func absolute(_ absoluteDimension: CGFloat) -> Self
class func estimated(_ estimatedDimension: CGFloat) -> Self
}
์ด ๋ค ๊ฐ์ง ํ์ ์ผ๋ก ๊ตฌ๋ถํ ์ ์๊ณ , ๊ฐ๊ฐ์ ์ฝ๊ฒ ์ค๋ช ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
fractional
: ์์ ์ปดํฌ๋ํธ(์ปจํ ์ด๋)์ ํฌ๊ธฐ์ ๋น๋กํ์ฌ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง ๋ ์ฌ์ฉ๋ฉ๋๋ค..fractionalWidth(0.5)
,.fractionalHeight(0.3)
absolute
: ๊ณ ์ ๋ ๊ฐ์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง ๋ ์ฌ์ฉ๋ฉ๋๋ค..absolute(200)
estimated
: ๊ณ ์ ๋ ๊ฐ์ผ๋ก ์์๋์ง๋ง ํฌ๊ธฐ๊ฐ ๋ณ๋๋ ๋ ์ฌ์ฉ๋ฉ๋๋ค..estimated(200)
Item
Item
์ ํ๋ฉด์ ๋ ๋๋ง๋๋ ์์์
๋๋ค.
cell
์ด๋ supplementary view
๊ฐ ์ฌ๊ธฐ์ ํด๋น๋ฉ๋๋ค.
class NSCollectionLayoutItem {
convenience init(layoutSize: NSCollectionLayoutSize)
var contentInsets: NSDirectionalEdgeInsets
}
์ด๊ธฐํํ ๋ size
๋ฅผ ์ ํด์ค์ผํ๊ณ , contentInsets
๋ฅผ ํตํด inset๋ ์ ํด์ค ์๊ฐ ์๋ค์.
Group
Group
์ ๋ ์ด์์์ผ๋ก์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋จ์๊ฐ ๋๋ ์์์
๋๋ค.
Group
์ .horizontal
, .vertical
, .custom
์ ์ธ๊ฐ์ง ํํ๋ก ์ ์ํด์ค ์ ์์ต๋๋ค.
๊ฐ๊ฐ์ Group
์ ํ๋์ ์์ FlowLayout
์ด๋ผ๊ณ ์๊ฐํด๋ณด๋ฉด ์ดํด๊ฐ ์ฝ์ต๋๋ค.
๊ฐ๋ก ํน์ ์ธ๋ก ํ์ชฝ ๋ฐฉํฅ์ผ๋ก ์ญ ์ด์ด์ง๋ ๋ ์ด์์์ด๋๊น์.
ํ ์ชฝ ๋ฐฉํฅ์ผ๋ก๋ง ์งํ๋๋ Group
์ด ์ซ๋ค๋ฉด .custom
์ ์ฌ์ฉํด ์ง์ ๊ตฌํํด์ค ์๋ ์์ต๋๋ค.
class NSCollectionLayoutGroup: NSCollectionLayoutItem {
class func horizontal(
layoutSize: NSCollectionLayoutSize,
subitems: [NSCollectionLayoutItem]) -> Self
class func vertical(
layoutSize: NSCollectionLayoutSize,
subitems: [NSCollectionLayoutItem]) -> Self
class func custom(
layoutSize: NSCollectionLayoutSize,
itemProvider: NSCollectionLayoutGroupCustomItemProvider) -> Self
}
Section
Section
์ ๋ง ๊ทธ๋๋ก Section
์ ๊ธฐ์ค์ผ๋ก ๋์ด๋๋ CollectionView
์ Section
์
๋๋ค.
๊ธฐ์กด์ ์ฌ์ฉํ๋ Layout๋ค์ Section
๊ณผ ๋์ผํ ๊ฐ๋
์
๋๋ค.
dataSource
๋ก๋ถํฐ Item
์ ๊ฐ์๋ฅผ ๋ฐ์์ Section
์ ๊ตฌ์ฑํฉ๋๋ค.
class NSCollectionLayoutSection {
convenience init(layoutGroup: NSCollectionLayoutGroup)
var contentInsets: NSDirectionalEdgeInsets
}
Layout
๊ทธ๋ ๋ค๋ฉด ๋ ์ด์์์ ์ด๊ธฐํํด์ค๋๋ ์ด๋ป๊ฒ ํ๋ฉด ๋ ๊น์?
์ ํ์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
class UICollectionViewCompositionalLayout: UICollectionViewLayout {
init(section: NSCollectionLayoutSection)
init(sectionProvider: @escaping SectionProvider)
}
init(section: NSCollectionLayoutSection)
๋ ์ด์์์ ์น์ ์ ์ง์ ์ง์ ํด์ฃผ๋ ๋ฐฉ์์ ๋๋ค.
์ฝ๊ณ ๊ฐ๋จํ์ง๋ง ํ์ฌ ์ฌ์ฉ๋๋ ๋ ์ด์์ ๋ฐฉ์๊ณผ ์ฐจ๋ณ์ฑ์ด ๊ฑฐ์ ์์ต๋๋ค.
init(sectionProvider: @escaping SectionProvider)
Compositionalํ๊ฒ Section
๋ค์ ๊ตฌ์ฑํด์ฃผ๋ ค๋ฉด ์ด ๋ฐฉ์์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
@escaping
ํด๋ก์ ๊ฐ ๋ณด์ด์์ฃ ?
์ด ํด๋ก์ ์์์ ๊ฐ Section
๋ง๋ค์ ๋
๋ฆฝ์ ์ธ ์ค์ ์ ํด์ค ์ ์์ต๋๋ค.
์ ํํ ํด๋น ๊ณผ์ ์ด ์ด๋ป๊ฒ ์ด๋ฃจ์ด์ง๋๊ฐ๋ ํ์ฉ ์์์์ ๋ค์ ํ๋ฒ ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ํ์ฉ ์์
๊ฐ๋จํ ํ ์ด๋ธ ๋ฆฌ์คํธ
private func configureLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subItems: [item]
)
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
๊ฐ์ฅ ๊ฐ๋จํ ํํ์ Layout์ด๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
ํ๊ฐ์ง ์ฃผ๋ชฉํ ์ ์ ์ด ๊ฒฝ์ฐ์ ๊ฐ cell์ size
๋ฅผ Item
์ด ์๋๋ผ Group
์ ํ์ฉํด์ ์ ํด์ฃผ์๋ค๋ ์ ์
๋๋ค.
.horizontal
๋ฐฉํฅ์ Group
์๋ ๊ฐ๊ฐ ํ๋์ Item
๋ง์ ๊ฐ๊ณ ์๋ ํํ์ด๊ธฐ ๋๋ฌธ์ ํ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์
๋๋ค.
๊ทธ๋์ itemSize
์ ํฌ๊ธฐ๋ width, height ๋ชจ๋ .fractional(1.0)
์ผ๋ก ๊ทธ๋ฃน์ ๊ฝ ์ฑ์์ฃผ์์ฃ .
๋์ groupSize
์ ํฌ๊ธฐ๋ฅผ width๋ .fractional(1.0)
, height๋ฅผ .absolute(44)
๋ก ์ง์ ํด์ฃผ์ด ๊ฐ๋ก๋ฅผ ๊ฝ ์ฑ์ฐ์ง๋ง ๋์ด๋ 44๋ก ๊ณ ์ ์์ผ์ฃผ์์ต๋๋ค.
ํ ์ค์ ๊ฐ์๊ฐ 5๊ฐ๋ก ๊ณ ์ ๋ ๊ทธ๋ฆฌ๋
์ด๋ฒ์๋ ๋ฌด์กฐ๊ฑด ํ ์ค์ ๋ฌด์กฐ๊ฑด 5๊ฐ์ cell์ด ๋ค์ด๊ฐ๊ณ , ์ ์ฌ๊ฐํ์ ํํ๋ฅผ ๊ฐ์ ธ์ผ ํ๋ ๊ทธ๋ฆฌ๋ ํ์์ ์ปฌ๋ ์ ๋ทฐ์ ๋๋ค.
์ด ๊ฒฝ์ฐ๋ ์์์ ์ฌ์ฉํ๋ ์ฝ๋์์ size
๊ฐ๋ง ๋ณ๊ฒฝํด์ฃผ๋ ๊ฒ ๋ง์ผ๋ก๋ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
private func configureLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subItems: [item]
)
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
๊ฐ ์์ดํ
๋ค์ width๋ ๊ทธ๋ฃน ๋์ด์ 20% (.fractionalWidth(0.2)
)๋ก, ๊ฐ ๊ทธ๋ฃน๋ค์ height๋ ์น์
๋์ด์ 20% (.fractionalWidth(0.2)
)๋ก ์ง์ ํด์ฃผ์ด ์ ์ฌ๊ฐํ์ ํํ๋ฅผ ๋ง๋ค์ด์ฃผ์์ต๋๋ค.
private func configureLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subItems: [item]
)
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
์ด ๋ถ๋ถ๋ง ์ถ๊ฐํด์ฃผ๋ฉด ๋ฉ๋๋ค!
inset
์ ์ฅ์ ์ Layout์ ๋ณํ ์์ด๋ ์ค์ ๋ก ๋ณด์ฌ์ง๋ ๊ฐ cell์ ํฌ๊ธฐ๋ฅผ ๋ณ๊ฒฝํด์ค ์ ์๋ค๋ ์ ์
๋๋ค.
์ด๋ฒ์๋ ๋น์ทํ์ง๋ง ๊ทธ๋ฆฌ๋ ํํ์ง๋ง ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ๊ตฌํํด๋ด ์๋ค.
private func configureLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
repeatingSubItem: item,
count: 2
)
let spacing: CGFloat = 10.0
group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = spacing
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
์ด ๊ฒฝ์ฐ์ itemSize
์ width์ height๋ฅผ ๋ชจ๋ .fractional(1.0)
์ผ๋ก ์ฃผ์์ง๋ง, ์ด ๊ฐ์ ์๋์ Group
์ค์ ์์ override๋ฉ๋๋ค.
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subItem: item,
count: 2
)
์ด๋ ๊ฒ Item
์ ๊ฐ์๋ฅผ ๊ทธ๋ฃน๋ณ๋ก ์ง์ ํด์ฃผ๋ฉด, CompositionalLayout
์ด ์์์ Item
์ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ ํด์ค๋๋ค.
์ฌ๋ฌ๊ฐ์ Section์ ๊ฐ๋ ๊ฒฝ์ฐ
์ง๊ธ๊น์ง๋ Section
์ด ํ๋์ธ ๊ฒฝ์ฐ, ์ฆ FlowLayout
์ผ๋ก๋ ๋ณ ์ด๋ ค์ ์์ด ๊ตฌํํ ์ ์์๋ ๊ฒฝ์ฐ๋ค์ด์์ต๋๋ค.
CompositionalLayout
์ด ์ด๊ฑธ ์ํด์ ๋ฑ์ฅํ ๊ฑด ์๋์์ฃ .
์ฌ๋ฌ๊ฐ์ Section
์ ๊ฐ๊ณ , ๊ฐ๊ฐ์ ๊ด๋ฆฌํ ์ ์๋ ๊ฒฝ์ฐ๋ฅผ ์ดํด๋ด
์๋ค.
func createLayout() -> UICollectionViewLayout {
let layout = UICOllectionViewCompositionalLayout { sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment -> NSCollectionLayoutSection? in
guard let sectionLayoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil }
let columns = sectionLayoutKind.columnCount
// ์ ๊ฒฝ์ฐ์ ๋ง์ฐฌ๊ฐ์ง๋ก ์๋ group ์ค์ ์์ override๋จ
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupHeight = columns == 1 ?
NSCollectionLayoutDimension.absolute(44) :
NSCollectionLayoutDimension.fractionalWidth(0.2)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: groupHeight
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitem: item,
count: columns
)
}
}
๊ต์ฅํ ๊ต์ฅํ ๊ธธ์ด ๋ณด์ด์ง๋ง ํต์ฌ์ ํ๋์ ๋๋ค.
let layout = UICollectionViewCompositionalLayout { sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment -> NSCollectionLayoutSection? in
//
}
์ด ๋ถ๋ถ์ด์ฃ .
์ด ๋ชจ๋ ์ค์ ๋ค์ SectionProvider
ํด๋ก์ ์์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.
sectionIndex
๋ ๋ง ๊ทธ๋๋ก Section
์ index๊ณ , layoutEnvironment
๋ size๋ display scale๊ณผ ๊ฐ์ container์ ํ๋กํผํฐ๋ค์ ์ ๊ณตํด์ฃผ๋ ํ๋กํ ์ฝ์
๋๋ค.
SectionLayoutKind
๋ sectionIndex
๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ Section
์ ์ข
๋ฅ๋ฅผ ๊ฒฐ์ ํด์ฃผ๊ณ , ํ๋์ column์ ๋ช๊ฐ์ Item
์ด ๋ค์ด๊ฐ๋์ง๋ฅผ ์ ํด์ฃผ๋ enum
ํ์
์
๋๋ค.
enum SectionLayoutKind: Int, CaseIterable {
case list, grid5, grid3
var columnCount: Int {
switch self {
case .grid3:
return 3
case .grid5:
return 5
case .list:
return 1
}
}
}
์ด๋ ๊ฒ Section
์ index์ ๋ฐ๋ผ์ ์ฌ๋ฌ ์ข
๋ฅ์ Layout์ ์ง์ ํด์ค ์ ์์ต๋๋ค.