Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- Clean Code
- Observable
- Xcode
- HIG
- ribs
- swift documentation
- SWIFT
- 애니메이션
- Human interface guide
- clean architecture
- uitableview
- UICollectionView
- RxCocoa
- 리펙토링
- collectionview
- combine
- 리팩토링
- rxswift
- uiscrollview
- 스위프트
- UITextView
- 클린 코드
- Refactoring
- ios
- tableView
- map
- 리펙터링
- MVVM
- Protocol
- swiftUI
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 2. iOS 스러운, storyboard 활용 방법 (dynamic prototypes cell, unwind segue, storyboard reference) 본문
HIG(Human Interface Guidelines)/HIG - UI
[iOS - swift] 2. iOS 스러운, storyboard 활용 방법 (dynamic prototypes cell, unwind segue, storyboard reference)
jake-kim 2021. 5. 5. 18:511. iOS 스러운, storyboard 활용 방법 (static prototype cell, segue, gesture)
2. iOS 스러운, storyboard 활용 방법 (dynamic prototype cell, unwind segue, storybaord reference)
플레이어 목록 DataSource 추가
- Player 데이터 추가
struct Player {
var name: String?
var game: String?
var rating: Int
}
- PlayersDataSource 추가
- PlayersDataSource
import UIKit
class PlayersDataSource {
// MARK: - Properties
var players: [Player]
static func generatePlayersData() -> [Player] {
return [
Player(name: "Bill Evans", game: "Tic-Tac-Toe", rating: 4),
Player(name: "Oscar Peterson", game: "Spin the Bottle", rating: 5),
Player(name: "Dave Brubeck", game: "Texas Hold 'em Poker", rating: 2)
]
}
// MARK: - Initializers
init() {
players = PlayersDataSource.generatePlayersData()
}
// MARK: - Datasource Methods
func numberOfPlayers() -> Int {
players.count
}
func append(player: Player, to tableView: UITableView) {
players.append(player)
tableView.insertRows(at: [IndexPath(row: players.count-1, section: 0)], with: .automatic)
}
func player(at indexPath: IndexPath) -> Player {
players[indexPath.row]
}
}
PlayersViewController 추가
- property 추가
class PlayersViewController: UITableViewController {
var playersDataSource = PlayersDataSource()
}
Data를 담는 Cell 추가
- Cell의 높이 설정 - tableView에서 Row Height 설정
- PlayersViewController의 TableView를 Static Cells에서 Dynamic Prototypes로 변경
- PlayerCell 추가: 주의할 점은 Cell이 Models에 속하는게 아닌 Views 그룹에 속하는 것
import UIKit
class PlayerCell: UITableViewCell {
static let identifier = "PlayerCell"
}
- XIB 연결
- class 연결
- identifier 설정
- Storyboard에서 드래그하여 IBOutlet 설정
class PlayerCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var gameLabel: UILabel!
@IBOutlet weak var ratingImageView: UIImageView!
}
- IBOutlet 설정 주의
- storyboard View에서 뷰 컴포넌트 자체를 끌어다가 바로 Outlet 작성하지 않고
- 컴포넌트에 마우스를 대고 ctrl + 왼쪽 클릭하여 아래처럼 Referncing Outlets에서 동그런 원을 끌어다가 연결할 것 (Outlet이 중복으로 생기는 것을 방지하고 더욱 간편한 방법)
- 데이터 binding 추가
class PlayerCell: UITableViewCell {
static let identifier = "PlayerCell"
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var gameLabel: UILabel!
@IBOutlet weak var ratingImageView: UIImageView!
var player: Player? {
didSet {
guard let player = player else { return }
gameLabel.text = player.game
nameLabel.text = player.name
ratingImageView.image = image(forRating: player.rating)
}
}
private func image(forRating rating: Int) -> UIImage? {
let imageName = "\(rating)Stars"
return UIImage(named: imageName)
}
}
PlayersViewController에서 cell 사용
- DataSource delegate 구현
// MARK: - UITableViewDataSource
extension PlayersViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
playersDataSource.numberOfPlayers()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PlayerCell.identifier) as! PlayerCell
cell.player = playersDataSource.player(at: indexPath)
return cell
}
}
PlayerDetailsViewController 추가
- IBOutlet 설정
class PlayerDetailsViewController: UITableViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var detailLabel: UILabel!
}
- 초기화 코드 추가
class PlayerDetailsViewController: UITableViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var detailLabel: UILabel!
var player: Player?
var game = "" {
didSet {
detailLabel.text = game
}
}
// MARK: - View Lifecycle
override func viewDidLoad() {
game = "Chess"
nameTextField.becomeFirstResponder()
}
}
unwind segue
- 생성된 segue를 되돌리는 기능
- unwind 작성 방법: unwind를 진행하는 화면이 아닌, unwind 결과로 나오는 화면에 @IBAction코드 작성
- cancel버튼, Done버튼에 관한 @IBAction Code 작성
// PlayersViewController
extension PlayersViewController {
@IBAction func cancelToPlayersViewController ( _ segue : UIStoryboardSegue ) {
}
@IBAction func savePlayerDetail ( _ segue : UIStoryboardSegue ) {
}
}
- unwind segue 연결
- Done 버튼의 Unwind segue 동작 시 데이터 추가하는 기능은 뒤에서 추가
새 플레이어 만들기 - GamePickerViewController 추가
- GamesDataSource 추가
import UIKit
class GamesDataSource {
// MARK: - Properties
var games = [
"Angry Birds",
"Chess",
"Russian Roulette",
"Spin the Bottle",
"Texas Hold'em Poker",
"Tic-Tac-Toe"
]
var selectedGame: String? {
didSet {
if let selectedGame = selectedGame,
let index = games.firstIndex(of: selectedGame) {
selectedGameIndex = index
}
}
}
var selectedGameIndex: Int?
// MARK: - Datasource Methods
func selectGame(at indexPath: IndexPath) {
selectedGame = games[indexPath.row]
}
func numberOfGames() -> Int {
games.count
}
func gameName(at indexPath: IndexPath) -> String {
games[indexPath.row]
}
}
- GamePickerViewController 추가
class GamePickerViewController: UITableViewController {
let gamesDataSource = GamesDataSource()
}
- XIB에 연결
- Cell 설정: Static Prototypes -> Dynamic Prototypes
- Cell나머지 삭제, Identifier설정
- 따로 CustomCell.swift하여 사용하지 않을것이기 때문에, cell style을 설정해주지 않으면 겹쳐보이는 버그 발생
Custom -> Basic으로 변경
- DataSource delegate 구현
- cell.accessoryType으로 checkmark속성 부여
- dequeueReusableCell(withIdentifier:for:)이 함수는 optional로 반환하지 않는 장점 존재
// GamePickerViewController.swift
// MARK: - UITableViewDataSource
extension GamePickerViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
gamesDataSource.numberOfGames()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath)
cell.textLabel?.text = gamesDataSource.gameName(at: indexPath)
if indexPath.row == gamesDataSource.selectedGameIndex {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
}
cell을 탭할 때 unwind segue
- 뒤로가기 시 나오는 화면인 PlayerDetailsViewController에 unwind segue 데이터를 받는 코드 추가
extension PlayerDetailsViewController {
@IBAction func unwindWithSelectedGame(segue: UIStoryboardSegue) {
if let gamePickerViewController = segue.source as? GamePickerViewController,
let selectedGame = gamePickerViewController.gamesDataSource.selectedGame {
game = selectedGame
}
}
}
- Cell 선택 > Exit으로 Ctrl+드래그 > UnwindWithSelectedGameWithSegue 선택
Segue로 매개변수 전달
- PlayerDetailsViewController -> GamePickerViewController: 선택한 game이 체크박스로 표현 기능
- segue를 참조하기 위해 "PickGame" Identifier 지정
- prepare(for:) 함수를 통해 segue 참조 가능 - PlayerDetailsViewController에 추가
// PlayerDetailsViewController.swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PickGame",
let gamePickerViewController = segue.destination as? GamePickerViewController {
gamePickerViewController.gamesDataSource.selectedGame = game
}
}
}
- GamePicker의 데이터 소스 delegate 구현
// GamePickerViewController.swift
// MARK: - UITableViewDelegate
extension GamePickerViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 셀을 선택할 경우 나타나는 회색 배경 제거
tableView.deselectRow(at: indexPath, animated: true)
// 이전에 선택한 cell의 checkmark UI 제거
if let index = gamesDataSource.selectedGameIndex {
let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0))
cell?.accessoryType = .none
}
// dataSource에서 새 게임 선택
gamesDataSource.selectGame(at: indexPath)
// checkmark UI 표출
let cell = tableView.cellForRow(at: indexPath)
cell?.accessoryType = .checkmark
}
}
- unwind 수정: 기존의 unwind는 cell를 선택할 때 위 구현부 전에 실행되어서 데이터가 업데이트 안되는 현상이 존재
- 기존의 unwind를 제거한 후, 코드에서 unwind를 호출 - identifier 정보 추가: 위 cell?.accessory = .checkmark 바로 밑에 작성
- 해당 id는 storyboard에서 참조될 값
extension GamePickerViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
...
performSegue(withIdentifier: "unwind", sender: cell)
}
}
- 기존의 GameCell에 연결되어 있던 unwind 제거 후 다시 연결
- 추가된 segue의 identifier입력
Done을 탭한 경우, 새 플레이어 저장
- PlayerDetailsViewController의 prepare(for: sender:)에 Done버튼이 눌린 경우 위에서 선언한 property에 저장하는 로직 추가
- 본 화면이 Done버튼을 클릭해서 뒤로 갈때 "SavePlayerDetail" segue가 동작
// PlayerDetailsViewController.swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifer == "PickGame", ... {
...
}
if segue.identifier == "SavePlayerDetail",
let playerName = nameTextField.text,
let gameName = detailLabel.text {
player = Player(name: playerName, game: gameName, rating: 1)
}
}
- PlayerViewsViewController에서 unwind segue로 온 데이터 획득한 후 처리
- savePlayerDetails 부분 내용 추가 (storyboard에서 segue 부분의 identifier에도 기입 필요)
// PlayersViewController.swift
extension PlayersViewController {
@IBAction func cancelToPlayersViewController(_ segue: UIStoryboardSegue) {
}
@IBAction func savePlayerDetail(_ segue: UIStoryboardSegue) {
guard
let playerDetailsViewController = segue.source as? PlayerDetailsViewController,
let player = playerDetailsViewController.player
else {
return
}
playersDataSource.append(player: player, to: tableView)
}
}
Storyboard Reference
- 하나의 storyboard reference로 만들 ViewController들을 드래그하여 선택
- Editor -> Refactor to Storyboard... 선택
- 이름 설정
- path 설정 주의: default는 Target바로 하위로 이동
- 결과: reference로 생성되었으며, Player.storyboard라는 별도의 파일로 생성
- reference 객체의 속성을 보면, Player.storyboard로 지정되어 있는 것을 확인
* segue 정리
|
|
* 참고
- 개념: www.raywenderlich.com/5055396-ios-storyboards-segues-and-more
'HIG(Human Interface Guidelines) > HIG - UI' 카테고리의 다른 글
[iOS - HIG] 2. 인터페이스에 필수로 들어가야할 요소 (0) | 2021.05.09 |
---|---|
[iOS - HIG] 1. 다른앱과 차별하고 있는 iOS의 테마 (0) | 2021.05.09 |
[iOS - swift] TableView Cell 삭제, 편집, 이동 (snapshot, UIGrphics) (4) | 2021.05.07 |
[iOS - swift] Modal 스타일 (Transition Style, Presentation) (0) | 2021.05.05 |
[iOS - swift] 1. iOS 스러운, storyboard 활용 방법 (static prototype cell, segue, gesture) (0) | 2021.05.04 |
Comments