[iOS] UICellAccessory 종류 알아보기
들어가기 전에
세상엔 셀을 꾸밀 수 있는~~ 멋진 액세서리들이 많아여~~~ 셀 꾸미기~ 셀꾸~
위 캡쳐에서 ⛺️ 빼고 다 iOS 에서 기본적으로 제공하는 cell 액세서리인데여, 걍 이런 식으로 추가하면 됨요! 멋지져?!
cell.accessories = [
.checkmark(),
.delete(),
.reorder()
]
🤔 : 엥? 난 cell 에 accessories
프로퍼티가 있는거 처음 보는데?
후후 이 cell 은 사실 UICollectionViewListCell
입니다. 이 타입은 cell 을 꾸밀 수 있는 accessories
프로퍼티를 가지고 있져
오늘 글에서는 UICollectionViewListCell
이 무엇이냐! 를 살펴보진 않고,, 그저 accessories
안에 들어갈 수 있는 UICellAccessory
는 무엇이고 어떤 종류들이 있냐! 를 알아볼겁니다.
그럼 ㄱㄱ~~!
UICellAccessory
UICellAccessory
는 UICollectionViewListCell
에 추가할 수 있는 시각적 요소로, iOS 14.0 부터 사용 가능합니다.
UIKit 은 표준 시스템 cell accessory 들을 정의해놨는데요! 시스템 액세서리의 위치는 미리 정의되어 있습니다. 시스템은 렌더링 순서와 셀의 어느 쪽에 표시되는지를 자동으로 결정합니다. array 에 있는 시스템 액세서리의 순서는 실제 배치 순서에 영향을 주지 않습니다.
이렇게 미리 정의된 액세서리의 모양, 위치(leading / trailing) 등은 변경할 수 없지만 색상과 같은 일부 값은 커스텀 할 수도 있습니다.
만약 한개 이상의 동일한 시스템 accessory 를 추가한다면
cell.accessories = [
.delete(),
.delete(),
]
system 은 이렇게 exception 을 throw 합니다.
Thread 1: "Accessories array contains more than one system accessory of the same type. Duplicate accessories: <UICellAccessoryDelete: 0x600002ab6080> <UICellAccessoryDelete: 0x600002ab60c0>"
흠냐리 바로 코드로 볼까요?
전체 코드는 Implementing Modern Collection Views 의 ReorderableListViewController
를 대충 수정했습니다.
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Emoji> { (cell, indexPath, emoji) in
var contentConfiguration = UIListContentConfiguration.valueCell()
contentConfiguration.text = emoji.text
cell.contentConfiguration = contentConfiguration
}
이렇게 표시되는 cell 이 있구여?
이제 cell 에 모든 액세서리를 냅다 추가해보겠습니다.
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Emoji> { (cell, indexPath, emoji) in
var contentConfiguration = UIListContentConfiguration.valueCell()
contentConfiguration.text = emoji.text
cell.contentConfiguration = contentConfiguration
cell.accessories = [
.outlineDisclosure(displayed: .always),
.disclosureIndicator(displayed: .always),
.delete(displayed: .always),
.reorder(displayed: .always),
.checkmark(displayed: .always),
.insert(displayed: .always),
.multiselect(displayed: .always),
.label(text: "naljin", displayed: .always),
.detail( displayed: .always),
.popUpMenu(
UIMenu(children: [
UIAction(title: "첫번째", handler: { _ in }),
UIAction(title: "두번째", handler: { _ in })
]),
displayed: .always)
]
}
홀리 몰리 뭔가 많이 생겼네요~~~
아까 “array 에 있는 시스템 액세서리의 순서는 실제 배치 순서에 영향을 주지 않습니다.” 라고 했져? 실제로 배열의 첫번째에는 outlineDisclosure
가 설정되어있지만, UI 상으로 가장 먼저 보이는건 delete
액세서리네여.
그럼 하나씩 살펴보러 갑시다 ㄱㄱ
delete
위치 : leading
모양 : 빨간색 circle 안에 minus 사인이 있음
정의
static func delete(
displayed: UICellAccessory.DisplayedState = .whenEditing,
options: UICellAccessory.DeleteOptions = DeleteOptions(),
actionHandler: UICellAccessory.ActionHandler? = nil
) -> UICellAccessory
흠.. 생소한 파라미터들이 있네요.. 함 보자고요
displayed
accessory 가 표시되는 상태(조건)를 설명합니다.
displayed
의 타입인 UICellAccessory.DisplayedState
은 enum 으로, 아래와 같은 세가지 case 가 있습니다.
always
- accessory 가 언제나 표시됩니다.whenEditing
- cell 이 editing mode 일때만 accessory 가 표시됩니다.whenNotEditing
- cell 이 editing mode 가 아닐때만 accessory 가 표시됩니다.
그래서
cell.accessories = [.delete()]
처럼 설정하면 기본적으로 delete 액세서리는 안보임여. 왜냐면 delete
accessory 의 displayed
기본 값은 whenEditing
이니까요.
collectionView.isEditing = true
처럼 editing mode 일때는 보이겠져?
만약 editing mode 와 관계없이 무조건 보이게하고 싶으면, .delete(displayed: .always)
로 만들면 됩니다.
options
자자 두번째 파라미터인 delete accessory 를 위한 설정 옵션입니다.
어떤 옵션이 가능한지 UICellAccessory.DeleteOptions
를 살펴보면
UICellAccessory.DeleteOptions.init(
isHidden: Bool? = nil,
reservedLayoutWidth: UICellAccessory.LayoutDimension? = nil,
tintColor: UIColor? = nil,
backgroundColor: UIColor? = nil
)
tintColor
랑 backgroundColor
는 대충 감이올텐데여, 각각 빨강, 초록색으로 설정하고 돌려봅시다
UICellAccessory.delete(displayed: .always,
options: .init(tintColor: .red,
backgroundColor: .green))
예상대로 나왔나여?
그럼 isHidden
으로 넘어가봅시다.
🤔 : 액세서리의 hidden 여부는 이미 displayed
에서 설정된거 아님?
과연 그럴까여? displayed: .always
로 설정한 상태에서 isHidden
을 true
로 설정해봅시다.
UICellAccessory.delete(displayed: .always,
options: .init(isHidden: true))
두둥 액세서리의 영역 자체는 잡히는데 해당 뷰가 보이지는 않네여. ㅇㅋㅇㅋ isHidden
뭔말인지 이해!
이제 옵션에서 마지막으로 남은 reservedLayoutWidth
으로 넘어가볼게여.
정의에는 “시스템이 accessory 를 위해 지정한 너비. 이 가운데에 accessory 를 배치함” 이라고 나와있어여
이 속성을 사용하면 액세서리의 크기가 다양한 경우에도, 시스템 및 커스텀 액세서리를 일관되게 수평으로 정렬할 수 있습니다.
무슨 말인지 모르겠져? 그림으로 살펴봅시다
여기에는 세 개의 서로 다른 셀이 있습니다. 각 셀은 이미지와 텍스트가 있네여.
여기서 각 셀들이 약간 다른 width 를 가진 이미지를 사용한다면 cell 사이의 정렬이 깨집니다.
이런 현상이 발생하는 이유는 이미지가 leading
으로 정렬된 상태에서, 텍스트가 각 이미지의 trailing
edge 에서 동일한 padding 으로 배치되기 때문입니다.
이러한 cell 사이에서 올바른 정렬을 위해서는 각 이미지에 대해 reserved layout size 를 지정해야 합니다. 각 configuration 에서 이미지에 대해 동일한 reserved layout width 를 설정하는 경우, 예약된 공간(reserved amount of space) 내에서 이미지를 수평으로 가운데에 배치합니다.
아래 빨간색 점선 사이의 거리가 각 이미지에 대한 reserved layout width 입니다.
reserved layout size 는 이미지의 실제 크기에 영향을 주지 않습니다. 만약 이미지가 reserved layout size 보다 크면 해당 영역 밖으로 나갈 수 있습니다.
이미지에 reserved layout size 를 사용하면 text 도 올바르게 정렬되는데요, 이는 텍스트가 reserved layout 영역에 상대적으로 위치하기 때문입니다.
symbol image 를 사용하는 경우, UIKit는 standard
reserved layout size 를 자동으로 적용합니다. non-symbol 이미지에 대해서는 필요한 경우 수동으로 요청할 수 있습니다.
UICellAccessory.LayoutDimension
타입은 enum 으로 아래의 세가지 case 가 있습니다.
actual
- 액세서리 레이아웃은 실제 치수(dimension)를 사용standard
- 액세서리 레이아웃은 액세서리에 대한 시스템 표준 레이아웃 dimension 를 사용custom(CGFloat)
- 액세서리 레이아웃은 사용자가 지정한 값을 사용
아래 그림을 보면 족굼 감이 오실까여? 저도 딱 여러분들이 이해한만큼 이해한 상태임다.
actionHandler
사용자가 delete accessory 와 상호 작용 (클릭이죠 뭐..?) 할때 호출할 클로저입니다. 타입이 UICellAccessory.ActionHandler
이긴 한데 그냥 아래와 같은 typealias 예여.
typealias UICellAccessory.ActionHandler = () -> Void
그래서 결론적으로 요런 식으로 모든 인자를 넘겨서 삭제 액세서리를 생성할 수도 있습니다.
cell.accessories = [
.delete(displayed: .always,
options: .init(isHidden: false,
reservedLayoutWidth: .standard,
tintColor: .red,
backgroundColor: .green),
actionHandler: {
print("삭제 버튼 클릭됨")
})
]
이제 다음 액세서리로 넘어가보져! 대충 중요한 개념들은 다 봤으니 이 다음부터는 쭉쭉 넘어갈 수 있을거예요!!
insert
위치 : leading
모양 : 초록색 circle 안에 plus 사인이 있음
정의
static func insert(
displayed: UICellAccessory.DisplayedState = .whenEditing,
options: UICellAccessory.InsertOptions = InsertOptions(),
actionHandler: UICellAccessory.ActionHandler? = nil
) -> UICellAccessory
위에서 본 delete
액세서리와 매우 유사합니다.
얘도 displayed
의 기본 값이 whenEditing
이라서 editing mode 가 아니면 기본적으로 노출되지 않습니다.
options
타입은 UICellAccessory.InsertOptions
인데 초기화 인자들을 보면 똑같이 생김여
UICellAccessory.InsertOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?,
backgroundColor: UIColor?
)
ㅋㅋ 개껌이쥬? 다음
multiselect
위치 : leading
모양 : cell 의 선택 상태에 따라 달라짐. unselected 일때는 빈 circle 이고, selected 일때는 checkmark 로 표시된 채워진 circle.
만약 collectionView.allowsMultipleSelection
이 기본값인 false
로 되어있으면 한개만 선택이 가능하고
collectionView.allowsMultipleSelection = true
로 설정되어있으면 여러개 선택 할 수 있습니다.
정의
static func multiselect(
displayed: UICellAccessory.DisplayedState = .whenEditing,
options: UICellAccessory.MultiselectOptions = MultiselectOptions()
) -> UICellAccessory
얘도 displayed
의 기본 값이 whenEditing
이네여
그래서 사실
cell.accessories = [.multiselect()]
로 생성하면 collectionView.isEditing = true
처럼 editing mode 일때만 보여요.
근데 문제는 이렇게 하면 선택이 안먹는거예여?
왜 그러냐 넌 또.. 하고 찾아본 결과 collectionView 의 allowsSelectionDuringEditing
을 true
로 줘야하더라구염. 그럼 한개씩 선택 가넝합니다.
collectionView.allowsSelectionDuringEditing = true
만약 allowsMultipleSelectionDuringEditing
을 true
로 주면 여러 개도 선택 가넝해여!
collectionView.allowsMultipleSelectionDuringEditing = true
자 다시 돌아와서 저희 multiselect
보고 있었져?
static func multiselect(
displayed: UICellAccessory.DisplayedState = .whenEditing,
options: UICellAccessory.MultiselectOptions = MultiselectOptions()
) -> UICellAccessory
multiselect
의 options
파라미터는 UICellAccessory.MultiselectOptions
타입인데, 다른 액세서리에서 살펴본것과 다를 것 없슴다
UICellAccessory.MultiselectOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?,
backgroundColor: UIColor?
)
label
위치 : trailing
모양 : 내가 설정한 text (항목의 개수를 표시하는 것과 같이 짧은 텍스트를 표시하는데 사용)
정의
static func label(
text: String,
displayed: UICellAccessory.DisplayedState = .always,
options: UICellAccessory.LabelOptions = LabelOptions()
) -> UICellAccessory
text
는 라벨에 표시할 text 고, displayed
의 기본 값은 always
네여
options
의 타입은 UICellAccessory.LabelOptions
입니다.
UICellAccessory.LabelOptions.init(
isHidden: Bool? = nil,
reservedLayoutWidth: UICellAccessory.LayoutDimension? = nil,
tintColor: UIColor? = nil,
font: UIFont? = nil,
adjustsFontForContentSizeCategory: Bool? = nil
)
오 근데 여기는 못 보던 파라미터들이 있네요? font
는 뭐... 그냥 폰트일테니까 넘어가고!
adjustsFontForContentSizeCategory
는 content size category 에 따라 label 이 자동으로 font 를 조정하는지 여부를 결정하는 bool
값입니다. 그러니까 유저가 시스템에서 글꼴을 크게 변경하면 이 label 액세서리의 폰트도 크게 변경되냐!? 를 지정하는 값이겠졍?? 기본값은 true
래여.
checkmark
위치 : trailing
모양 : 기본 시스템 green 색상의 체크마크입니다.
아니 근데 설명은 이렇게 green 색상이라고 써있는데 누가봐도 파란색 아니예요..?? 제가 아는 green 이 아닌…?? 아시는 분 있으면 알려주십셔 plz
정의
static func checkmark(
displayed: UICellAccessory.DisplayedState = .always,
options: UICellAccessory.CheckmarkOptions = CheckmarkOptions()
) -> UICellAccessory
displayed
의 기본 값은 always
네요.
options
타입은 UICellAccessory.CheckmarkOptions
인데 얘도 뭐 새로울건 없군요
UICellAccessory.CheckmarkOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?
)
detail
위치 : trailing
모양 : 시스템 information 버튼
(근데 이름이 detail 이라고 하면 >
모양이 생각나지 않으세여? 저만 그런가.. 그냥 information 이라고 하지.. )
정의
static func detail(
displayed: UICellAccessory.DisplayedState = .always,
options: UICellAccessory.DetailOptions = DetailOptions(),
actionHandler: UICellAccessory.ActionHandler? = nil
) -> UICellAccessory
displayed
의 기본 값은 always
입니다.
options
의 타입은 UICellAccessory.DetailOptions
입니다.
UICellAccessory.DetailOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?
)
뻔해. 다음. 하고 넘어가기 전에 detail
액세서리는 최소 버전 15.4 부터 사용할 수 있다는 점이 특이 포인트입니다. 다른 애들이랑 똑같이 14.0 으로 맞추지.. 흥 제법 웃겨
popUpMenu
위치 : trailing
모양 : 위쪽과 아래쪽을 가리키는 한 쌍의 chevron.
정의
static func popUpMenu(
_ menu: UIMenu,
displayed: UICellAccessory.DisplayedState = .always,
options: UICellAccessory.PopUpMenuOptions = PopUpMenuOptions(),
selectedElementDidChangeHandler: UICellAccessory.MenuSelectedElementDidChangeHandler? = nil
) -> UICellAccessory
첫번째 menu
파라미터에는 아래와 같이 UIMenu
를 넣어주면 됩니다.
UICellAccessory.popUpMenu(
UIMenu(children: [
UIAction(title: "첫번째", handler: { action in print("첫번째 클릭")}),
UIAction(title: "두번째", handler: { action in print("두번째 클릭")})
])
)
displayed
기본 값은 always
입니다.
options
의 타입은 UICellAccessory.PopUpMenuOptions
입니다.
UICellAccessory.PopUpMenuOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?
)
그리고 마지막으로 넘겨 받는 selectedElementDidChangeHandler
는 menu 에서 이전에 선택한 것과 다른 element 를 선택할 때 호출하는 클로저입니다.
typealias UICellAccessory.MenuSelectedElementDidChangeHandler = (UIMenu) -> Void
근데 popUpMenu
나 아까 본 multiselect
나 둘 다 셀의 아무 곳이나 클릭해도 동작하는 애들이란 말이죠?
그럼 아래와 같이 두 액세서리 모두 설정을 한다면 어떻게 될까요?
cell.accessories = [
.multiselect(displayed: .always),
.popUpMenu(
UIMenu(children: [
UIAction(title: "첫번째", handler: { action in print("첫번째 클릭")}),
UIAction(title: "두번째", handler: { action in print("두번째 클릭")})
])
)
]
결과적으로 popUpMenu
의 동작이 우선시 되고 multiSelect
는 동작하지 않습니다. 이는 즉 didSelectItemAt
delegate 함수도 호출되지 않는다는 뜻이겠져.
이를 방지하기 위해서는… 한 10분 정도 구글링했을때는 안나오네여..? 미래의 제가 쓸일이 있으면 더 고생해주겠져 뭐.. ㅎㅎ ㅎ오늘도 미루는 삶!
아 팝업 메뉴 액세서리는 최소 버전 16 부터 사용할 수 있습니다.
disclosureIndicator
위치 : trailing
모양 : trailing 방향을 가리키는 disclosure chevron
정의
static func disclosureIndicator(
displayed: UICellAccessory.DisplayedState = .always,
options: UICellAccessory.DisclosureIndicatorOptions = DisclosureIndicatorOptions()
) -> UICellAccessory
displayed
의 기본 값은 always
입니다.
options
의 타입은 UICellAccessory.DisclosureIndicatorOptions
입니다.
UICellAccessory.DisclosureIndicatorOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?
)
outlineDisclosure
위치 : iOS 나 Mac Catalyst 의 헤더 — trailing, Mac Catalyst 의 cell — leading
모양 : disclosure 액세서리와 비슷하게 생겼지만 클릭시 회전됩니다.
만약 child item 을 넣어주면 이렇게 cell 이 확장 및 축소 되는것을 볼 수 있습니다.
만약 이 액세서리가 없으면 expand 가능한 child item 이 있어도 cell 확장이 되지 않습니다. 즉 cell 이 확장 / 축소 가능하다! 를 나타낼 (indicate) 뿐만 아니라, 그 기능 자체도 가능 (enable) 토록 하는 액세서리라고 할 수 있습니다.
Use this cell accessory to indicate that an item can expand and collapse, and to enable the user to toggle between the expanded and collapsed states.
정의
static func outlineDisclosure(
displayed: UICellAccessory.DisplayedState = .always,
options: UICellAccessory.OutlineDisclosureOptions = OutlineDisclosureOptions(),
actionHandler: UICellAccessory.ActionHandler? = nil
) -> UICellAccessory
displayed
의 기본 값은 always
입니다.
options
의 타입은 UICellAccessory.DisclosureIndicatorOptions
입니다.
UICellAccessory.DisclosureIndicatorOptions.init(
style: UICellAccessory.OutlineDisclosureOptions.Style?,
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?
)
오호.. 여기엔 처음 보는 style
이라는 파라미터가 있네여. 이름대로 outline disclosure 의 스타일을 나타낸다고 합니다.
style
의 타입은 UICellAccessory.OutlineDisclosureOptions.Style
enum 인데 아래와 같은 3가지 case 가 있습니다.
cell
- 중첩된 children 이 있는 선택 가능한 셀에 사용할 스타일. 이 스타일을 사용하면 outline disclosure 액세서리를 클릭하는 경우에만 expansion 상태가 변경됨. cell 자체를 클릭하면 item 에 대한 선택이 됨.header
- 섹션 헤더에 사용할 스타일. 이 스타일을 사용하면 cell 의 어느 곳을 클릭하든 expansion 상태가 변경됨. 따라서 이 스타일을 사용하면 cell 자체는 selectable 하지 않게 됨.automatic
- 셀의 configuration 이 섹션 헤더인지 여부에 따라 시스템이 자동으로 스타일을 결정
reorder
위치 : trailing
모양 : 기본 system gray 색상의 세 개의 수평선
컬렉션 뷰에서 순서 변경을 지원하는 경우, 사용자는 이 액세서리를 기준으로 셀을 드래그해서 순서를 변경할 수 있습니다.
그러려면 우선 reorderingHandlers
부터 잘 지정해줘야겠져?
dataSource.reorderingHandlers.canReorderItem = { item in return true }
dataSource.reorderingHandlers.didReorder = { transaction in
// 구현
}
정의
static func reorder(
displayed: UICellAccessory.DisplayedState = .whenEditing,
options: UICellAccessory.ReorderOptions = ReorderOptions()
) -> UICellAccessory
얘는 displayed
의 기본 값이 whenEditing
이네여.
options
타입은 UICellAccessory.ReorderOptions
입니다.
UICellAccessory.ReorderOptions.init(
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?,
showsVerticalSeparator: Bool?
)
오 showsVerticalSeparator
라는 새로운 파라미터가 있네여. 다른 액세서리가 앞에 있을때 그 사이에 vertical separator 표시할건지를 결정하는 값이라고 합니다. 기본 값은 true
래여
만약 자신 앞에 다른 액세서리가 없으면 vertical separator 가 없지만
임의로 checkmark
액세서리를 추가해보면? 요렇게 선이 생깁니다.
만약 showsVerticalSeparator
값을 false
로 주면 앞에 다른 액세서리가 있어도 separator 는 없어지겠져?
cell.accessories = [
.checkmark(),
.reorder(displayed: .always,
options: .init(showsVerticalSeparator: false)
)
]
custom
지금까지는 시스템에서 정의된 액세서리를 살펴봤는데요! custom view 를 이용한 accessory 를 만들수도 있습니다!
바로 이런식으로 말이져
let myView = UIView(frame: CGRect(origin: .zero,
size: CGSize(width: 10, height: 10)))
myView.backgroundColor = .red
cell.accessories = [
.customView(configuration: .init(customView: myView,
placement: .leading()))
]
보다시피 customView
라는 UICellAccessory
타입 메서드를 사용했는데요!
static func customView(configuration: UICellAccessory.CustomViewConfiguration) -> UICellAccessory
파라미터로 받는 configuration
의 타입을 살펴볼까요?
UICellAccessory.CustomViewConfiguration
의 초기화 함수는 아래와 같이 생겼습니다.
init(customView: UIView,
placement: UICellAccessory.Placement,
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?,
maintainsFixedSize: Bool?)
받는게 꽤 많네요. 그 중 다른 액세서리에서 못 본 파라미터들만 살펴보자면, 우선 customView
는 내가 액세서리로 나타낼 뷰입니다.
placement
파라미터는 액세서리의 위치를 결정합니다. UICellAccessory.Placement
타입은 enum 으로, 아래 두 케이스를 이용해서 cell 의 leading 혹은 trailing 에 액세서리를 배치할 수 있습니다.
case leading(displayed: UICellAccessory.DisplayedState, at: UICellAccessory.Placement.Position)
case trailing(displayed: UICellAccessory.DisplayedState, at: UICellAccessory.Placement.Position)
그러니까 간단하게 쓰려면 이런식으로 쓸 수 있단말이져
UICellAccessory.customView(configuration: .init(customView: myView,
placement: .leading()))
하지만 각 case 에서 associated value 로 받는 값들이 있으니까 함 볼까여? leading
부터 가봅시다.
case leading(
displayed: UICellAccessory.DisplayedState = .always,
at: UICellAccessory.Placement.Position = { $0.count }
)
displayed
의 기본값은 always
군여.
at
으로는 UICellAccessory.Placement.Position
을 받고 있네여. 이 타입은 다른 액세서리에 대한 인덱스 위치를 나타냅니다.
typealias UICellAccessory.Placement.Position = (_ accessories: [UICellAccessory]) -> Int
leading 에서 at
의 기본 값은 { $0.count }
로 설정이 되어있으니까 leading 에 있는 액세서리 중 가장 마지막에 위치하겠군여
실제로 leading 에 위치하는 액세서리인 insert
와 multiselect
를 같이 배치했을 때 customView
는 가장 마지막에 위치합니다.
cell.accessories = [
.insert(displayed: .always),
.customView(configuration: .init(customView: myView,
placement: .leading())),
.multiselect(displayed:.always)
]
그러면 UICellAccessory.Placement
의 다른 case 인 trailing
으로 넘어가볼게여.
case trailing(
displayed: UICellAccessory.DisplayedState = .always,
at: UICellAccessory.Placement.Position = { _ in 0 }
)
trailing 에서 at
의 기본 값은 { _ in 0 }
로 설정이 되어있으니까 trailing 에 있는 액세서리 중 가장 첫번째에 위치하겠군여
실제로 trailing 액세서리인 checkmark
와 reorder
를 같이 배치했을 때 customView
는 가장 첫번째에 위치합니다.
cell.accessories = [
.checkmark(),
.customView(configuration: .init(customView: myView,
placement: .trailing())),
.reorder(displayed: .always)
]
만약 custom 액세서리의 leading / trailing 을 정하는 것에 더해서, 특정 액세서리 앞 / 뒤인지까지 지정하고 싶어!! 라고 한다면 UICellAccessory.Placement
의 두 타입 메서드를 이용할 수도 있습니다
static func position(after: UICellAccessory) -> UICellAccessory.Placement.Position
static func position(before: UICellAccessory) -> UICellAccessory.Placement.Position
이런 식으로 말이져
UICellAccessory.customView(configuration: .init(customView: myView,
placement: .leading(at: UICellAccessory.Placement.position(after: .insert()))))
그래서 실제로 insert
와 delete
액세서리를 추가하고, custom 액세서리를 delete
뒤에 넣겠다! 라고 지정을 해주면
cell.accessories = [
.insert(displayed: .always),
.delete(displayed: .always),
.customView(configuration: .init(customView: myView,
placement: .leading(at: UICellAccessory.Placement.position(after: .delete()))))
]
요렇게 원하는대로 잘 나옵니다.
그런데 만약 custom 액세서리를 특정 액세서리 뒤에 위치 시킬거임!! 하고 position(after:)
를 사용했는데, 이때 "특정 액세서리" 가 cell 에 추가되지 않은 상태라면 어떻게 될까요?
아래처럼 코드처럼 지금 액세서리로 세팅이 안된 multiselect
뒤에 custom 액세서리를 설정하겠다! 라고 하는 상황처럼 말이져
cell.accessories = [
.insert(displayed: .always),
.delete(displayed: .always),
.customView(configuration: .init(customView: myView,
placement: .leading(at: UICellAccessory.Placement.position(after: .multiselect()))))
]
이럴때 position(after:)
는 custom accessory 를 가장 뒤에 위치시킵니다.
반대로 position(before:)
는 인자로 넘긴 액세서리가 없을때 custom accessory 를 가장 앞에 위치시킵니다.
cell.accessories = [
.insert(displayed: .always),
.delete(displayed: .always),
.customView(configuration: .init(customView: myView,
placement: .leading(at: UICellAccessory.Placement.position(before: .multiselect()))))
]
또 다른 상황으로 insert
, delete
, checkmark
액세서리를 추가하고, 커스텀 액세서리의 placement
는 leading
, position
은 checkmark
액세서리 뒤로 하겠다! 라고 해볼게요
cell.accessories = [
.insert(displayed: .always),
.delete(displayed: .always),
.checkmark(displayed: .always),
.customView(configuration: .init(customView: myView,
placement: .leading(at: UICellAccessory.Placement.position(after: .checkmark()))))
]
근데 여러분.. checkmark
는 기본적으로 trailing 에 위치한 애잖아여??
커스텀 액세서리 위치를 leading 으로 하겠다고 지정해놓고,,, trailing 에 있는 checkmark
뒤에 놓겠다..?? 라는 모순적인 상황이 되는거져
이러한 상황의 결과는, 액세서리가 셀에 추가되지 않았을때의 상황과 동일하게 position(after:)
는 custom accessory 를 가장 뒤에, position(before:)
는 custom accessory 를 가장 앞에 배치합니다.
휴 UICellAccessory.CustomViewConfiguration
의 초기화 함수를 보다가 여기까지 왔는데요!
init(customView: UIView,
placement: UICellAccessory.Placement,
isHidden: Bool?,
reservedLayoutWidth: UICellAccessory.LayoutDimension?,
tintColor: UIColor?,
maintainsFixedSize: Bool?)
마지막으로 maintainsFixedSize
만 보면 되겠네요!!
얘는 custom view 의 frame size 를 유지할건지 결정한다고 해요. 기본값은 false
입니다.
이 값이 true
로 설정되어있으면 system 은 view의 현재 frame size 를 유지하고, false
면 system 은 액세서리를 레이아웃하는 동안 view 의 크기를 조정합니다.
When the value of this property is
true
, the system preserves the current frame size of the view. When the value of this property isfalse
, the system sizes the view during layout of the accessories.
근데 사실 무슨 말인지도 잘 이해가 안가고,, 제가 true
나 false
로 확인해봤을때는 그냥 똑같아 보이는데.. 어떤 조건을 다르게 해야 좀 차이를 볼 수 있는지 모르겠슴다! 아시는 분 있으면 알려주세여!! (뻔뻔)
마무리
이렇게해서 list cell 에 설정할 수 있는 많은 액세서리들에 대해 하나씩 알아봤는데여 뭐 대충 이런게 있구나~~ 하고 알아가면 될 것 같슴다
그래도 글 쓰는데 시간 꽤나 들인거 같은데,, 읽는데는 너무 후루룩이네여..? 족굼 억울하지만 설날이니까 기분 좋게 넘어가는걸로~~~
그럼 모두 모두 해피 설날 보내세요~~!!!