Attributes inspector에서 table view cell의 Style을 'Custom'으로 설정한다.
stack을 적절히 삽입하여 원하는 cell style을 구성한다.
Constraint to margins
cell 내부의 1번째 stack의 'Constraint to margins'를 체크하여 cell과 stack 사이의 적절한 여백을 남긴다.
Content Hugging Priority
상대적인 숫자를 통해서 Auto Layout 엔진에게 content를 감쌀 view의 우선순위를 알려준다.
ex) emoji label의 horizontal content hugging priority를 251에서 252로 변경시켜 emoji label이 content를 감싸 수축되도록 Auto Layout에게 우선순위를 부여한다.
UITableViewCell
의 subclass를 생성하고, storyboard에서 cell의 custom class로 연결해준다.
각 label의 outlet을 생성하고, label을 업데이트하는 method를 생성한 후, 기존의 tableView(_:cellForRowAt:)
method를 수정한다.
tableView(_:cellForRowAt:)
method의 코드를 cell에게 넘김으로써 추상화를 만족시키는 일반적인 pattern이다.func update(with emoji: Emoji) {
symbolLabel.text = emoji.symbol
nameLabel.text = emoji.name
descriptionLabel.text = emoji.description
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
//Step 1: Dequeue cell
//dequeueReusableCell method는 UITableViewCell instance를 반환하므로 update method 사용을 위해 force-downcast를 통해 EmojiTableViewCell class로 변환한다.
let cell = tableView.dequeueReusableCell(withIdentifier: "EmojiCell", for: indexPath) as! EmojiTableViewCell
//Step 2: Fetch model object to display
let emoji = emojis[indexPath.row]
//Step 3: Configure cell
cell.update(with: emoji)
cell.showsReorderControl = true
//Step 4: Return cell
return cell
}
.none
.delete
.insert
table view가 editing 상태로 들어갈 때, 다음과 같은 순서로 data source, delegate method가 호출된다.
tableView(_:canEditRowAt:)
(data source method)tableView(_:editingStyleForRowAt:)
(delegate method)tableView(_:commit:forRowAt:)
(data source method)deleteRows(at:with)
또는 insertRows(at:with)
로 전송tableView(_:canEditRowAt:)
(data source method)tableView(_:editingStyleForRowAt:)
(delegate method)override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
tableView(_:commit:forRowAt:)
(data source method)override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
emojis.remove(at: indexPath.row)
// at: 구체적으로 삭제할 row의 indexpath array
// with: row animations (UITableView.RowAnimation enum)
tableView.deleteRows(at: [indexPath], with: . automatic)
}
}
row animation documentation 바로가기
1. 빈 row에서 .insert
control 사용
navigation bar에 Add(+) 버튼을 추가해서 사용자가 property를 작성할 수 있는 새로운 view controller(static table view) 띄움
(해당 view controller는 edit mode에서 재사용 가능)
Static Table Views
- 보여줄 row의 수와 cell의 type을 정확히 알고있는 table view
- row의 갯수가 변하지 않을 때 유용하다. (ex. Setting App)
- data source protocol을 작성하지 않고, table view data를 전달하기 위해
viewDidLoad()
method를 사용하는 table view controller를 갖는다.
(빈 data source method 사용을 통해 empty table view 가짐)
UITableViewController
의 subclass를 생성하여 해당 static table view의 Custom Class로 등록한다.
@IBSegueAction
을 사용하여 전달될 data(여기선 Emoji
)를 위해 custom initializer를 생성한다.
(Xcode 'fix-it'버튼을 통해 issue 수정)
init?(coder: NSCoder, emoji: Emoji?) {
self.emoji = emoji
super.init(coder: coder)
}
storyboard로 돌아가 list table view scene에 bar button을 추가하고 button의 system item을 'Add'로 설정한다.
modal presentation segue를 생성한다.
Human Interface Guideline
새로운 data를 생성하거나, 존재하는 data를 수정할 때 static table controller를 modal로 띄우는 modal presentation segue 사용
기존 navigation controller의 tableView(_:didSelectRowAt:)
method를 제거한다.
navigation controller와 static table view controller 사이의 relation controller에 대한 @IBSegueAction
을 기존 view controller에 생성한다.
@IBSegueAction func addEditEmoji(_ coder: NSCoder, sender: Any?) -> AddEditEmojiTableViewController? {
// sender가 cell이라면 edit mode
if let cell = sender as? UITableViewCell,
let indexPath = tableView.indexPath(for: cell) {
// Editing Emoji
let emojiToEdit = emojis[indexPath.row]
return AddEditEmojiTableViewController(coder: coder, emoji: emojiToEdit)
// sender가 cell이 아니라면 add mode
} else {
// Adding Emoji
return AddEditEmojiTableViewController(coder: coder, emoji: nil)
}
}
Attributes inspector에서 new table view controller scene의 Content를 'Static Cell'로 변경한다.
table view의 style을 설정(Grouped)하고 원하는 구조로 section의 수와 각 section의 row 수를 설정한다.
각 cell에 text field를 추가하고 constraints를 설정한다.
new table view controller에 각 text field의 outlet을 등록한다.
viewDidLoad()
method 내부에 instance가 값을 갖고 있는지 체크한다.
// Edit Mode
// 값이 존재한다면, 각 text field를 해당 instance의 property로 업데이트
if let emoji = emoji {
symbolTextField.text = emoji.symbol
nameTextField.text = emoji.name
descriptionTextField.text = emoji.description
usageTextField.text = emoji.usage
title = "Edit Emoji"
// Add Mode
// 값이 nil
} else {
title = "Add Emoji"
}
Human Interface Guideline
새로운 table view를 아래로 swipe함으로써 지울 수 있지만, dismiss button을 추가해주는게 좋다.
'Cancel', 'Save' style의 bar button item을 각각 생성한다.
기존 table view controller에 unwindToEmojiTableView(segue:)
method를 추가하고, Save, Cancel button을 Exit icon를 통해 method와 연결시킨다.
Save button에 identifier를 설정한다.
symbolTextField
가 single emoji character를 가져야만 save button이 활성화되도록 method를 추가한다.func containsSingleEmoji(_ textField: UITextField) -> Bool {
guard let text = textField.text, text.count == 1 else {
return false
}
let isCombinedIntoEmoji = text.unicodeScalars.count > 1 &&
text.unicodeScalars.first?.properties.isEmoji ?? false
let isEmojiPresentation = text.unicodeScalars.first?.properties.isEmojiPresentation ?? false
return isEmojiPresentation || isCombinedIntoEmoji
}
func updateSaveButtonState() {
let nameText = nameTextField.text ?? ""
let descriptionText = descriptionTextField.text ?? ""
let usageText = usageTextField.text ?? ""
saveButton.isEnabled = containsSingleEmoji(symbolTextField) &&
!nameText.isEmpty && !descriptionText.isEmpty &&
!usageText.isEmpty
}
중위연산자
??
두 값을 비교하여 왼쪽의 값이 nil이라면, 오른쪽 값을 반환
viewDidLoad()
에서 호출한다.override func viewDidLoad() {
super.viewDidLoad()
if let emoji = emoji {
symbolTextField.text = emoji.symbol
nameTextField.text = emoji.name
descriptionTextField.text = emoji.description
usageTextField.text = emoji.usage
title = "Edit Emoji"
} else {
title = "Add Emoji"
}
updateSaveButtonState()
}
@IBAction
을 생성하고 해당 method에 모든 text field를 연결한다.@IBAction func textEditingChanged(_ sender: UITextField) {
updateSaveButtonState()
}
Save 버튼이 눌렸을 때, saveUnwind
segue가 수행되고 collection이 업데이트될 수 있다.
segue가 호출되기 전, 새로운 instance를 생성하고 property를 세팅하기 위해 text field 값이 사용된다.
static table view controller에 saveUnwind
segue가 수행되었는지 확인 후 property를 업데이트하는 prepare(for segue:)
method를 추가한다.
Cancel 버튼을 누를 경우 업데이트를 하지 않는다.
text field의 optional value가 nil
값이 아님을 Save 버튼에서 확인했기 때문에 예외처리에 신경쓰지 않아도 된다.
override func prepare(for segue: UIStoryboardSegue,
sender: Any?) {
guard segue.identifier == "saveUnwind" else { return }
let symbol = symbolTextField.text!
let name = nameTextField.text ?? ""
let description = descriptionTextField.text ?? ""
let usage = usageTextField.text ?? ""
emoji = Emoji(symbol: symbol, name: name, description: description, usage: usage)
}
unwindToEmojiTableView(segue:)
method로 돌아와 saveUnwind
segue가 수행되었는지 확인 후, table view에 여전히 선택된 row가 있는지 체크한다.
선택된 row가 있는 경우
선택된 row가 없는 경우
@IBAction func unwindToEmojiTableView(segue: UIStoryboardSegue) {
guard segue.identifier == "saveUnwind",
let sourceViewController = segue.source as? AddEditEmojiTableViewController,
let emoji = sourceViewController.emoji else { return }
if let selectedIndexPath = tableView.indexPathForSelectedRow {
emojis[selectedIndexPath.row] = emoji
tableView.reloadRows(at: [selectedIndexPath], with: .none)
} else {
let newIndexPath = IndexPath(row: emojis.count, section: 0)
emojis.append(emoji)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
모든 view는 content hugging priority와 유사한 view의 수축을 어떻게 제한할 것인지 표현하는 compression resistance value를 가진다.
기본값은 750이며, 값이 클수록 Auto Layout engine은 수축을 피하기위해 다른 것보다 우선순위를 갖는다.
table view cell의 label에 긴 text가 입력된다면, text가 잘리는 것을 볼 수 있다.
tableView(_:heightForRowAt:)
을 작성하여 cell이 전체 text를 보여줄 수 있도록 height를 계산하는 방법이 있지만 성가시고 에러가 발생할 수 있다.
Auto Layout engine에게 view content의 우선순위를 제공한다.
Interface Builder 입장
Auto Layout engine 입장
해당 label의 vertical compression resistance를 주변값보다 높게 설정하여 text의 양에 따라 커질 수 있게 한다.
table view가 cell의 content에 따라 자동으로 row height을 결정할 수 있도록 한다.
viewDidLoad()
method에 아래의 코드를 추가한다.
UITableView.automaticDimension
을 통해 cell height을 계산함으로써 stack view height가 증가한다.
tableView.rowHeight = UITableView.automaticDimension
// 퍼포먼스 향상을 위해 cell의 평균 height를 제공
tableView.estimatedRowHeight = 44.0