[IOS/Swift] YouTube ToDoList 예제 카피, 분석하기
회사에서 IOS 교육프로젝트를 진행하게 됐다. 관련해서 미리 Swift 공부를 해보려한다. youtube에 따라하기 좋은 예제들이 많아 예제들을 따라하고, 사용된 클래스나 패턴에 대해서 분석해보고, 정보를 정리해보려한다.
내가 보고 따라한 예제는 아래링크이다. 그냥 멍하니 따라치면 간단하게 구현가능한 굉장히 쉬운 예제이지만, 관련한 클래스, 스킬들을 자세히 살펴보면, 의미있는 부분들이 있어 포스팅 해보려한다.
https://www.youtube.com/watch?v=U-Y9HJzNhQ0
해당 예제가 어떤 과정으로 이루어져있는지 하나하나 따라가보자!
목차
- 프로젝트 생성
1. Set up Storyboard
1.1 Main.stroyboard 이동
1.2 tableview 아이템 추가
1.3 Tableview Layoutmargins 설정
1.4 Navigation Controller 생성
2. TableView Outlet
2.1 ViewController.swift 이동
2.2 Class 전역 변수로 tableView 변수 추가
2.3 extension ViewController 코드 추가
2.4 storyboard item과 controller 변수 연결
2.4.1 main.storyboard 이동
2.4.2 storyboard의 ViewController 활성화 및 오른쪽클릭
2.4.3 TableView의 cell 추가
2.4.4 ViewController.swift 이동
2.5 "add" bar button 추가
2.5.1 main.storyboard로 이동
2.5.2 bar button item 추가
2.5.3 bar button item "Title" 변경
2.6 "add" bar button action 추가
2.6.1 ViewController.swift로 이동
2.6.2 @IBAction function 추가
3. Adding To Do List Entry
3.1 새로운 ViewController 추가 및 연결
3.1.1 왼쪽 Project navigator에서 New File, (EntryController.swift, TaskController.swift)
3.2 EntryViewController.swift 작업
3.2.1 EntryViewController.swift로 이동
3.2.2 @IBOutlet인 UITextField 변수 추가
3.2.3 UITextFieldDelegate 프로토콜 채택
3.2.4 UITextFieldDelegate 프로토콜 관련 코드 추가
3.3 storyboard의 EntryView, TaskView 작업
3.3.1 main.storyboard로 이동
3.4 View Controller 이동 작업(didAddTap())
3.4.1 ViewController.swift로 이동
3.4.2 didAddTap function 수행 코드 작성
3.5 saveTask() 관련코드 작성
3.5.1 ViewController.swift로 이동 및 관련코드 작성(title, delegate)
3.5.2 EntryViewController.swift로 이동
3.5.3 rightBarButton 추가
3.5.4 saveTask() 코드 작성
4. Saving UserDefaults
4.1 UserDefaults() 설정
4.1.1 ViewController.swift로 이동
4.1.2 UserDefaults() Setup
4.1.3 EntryViewController.swift로 이동
4.1.4 saveTask() 코드 추가
5. Passing Data with Closure
5.1 ViewController에서 활용한 Closure 추가
6. Getting Saved Data
6.1 updateTasks() 코드 작성
6.2 updateTasks() 코드 추가
7. Opening Task Details
7.1 cell click event 추가
7.2 TaskViewController.swift로 이동
7.3 task detail을 위한 item 추가
7.4 label 변수와 storyboard item 연결
- Delete Task 기능
- 프로젝트 생성
Product Name : 임의
Language : Swift
User Interface : Storyboard
Language, User Interface는 위와 같이 설정하고 프로젝트를 생성한다.
1. Set up Storyboard
1.1 Main.stroyboard 이동
뷰에 필요한 아이템들을 추가
1.2 tableview 아이템 추가
1.3 Tableview Layoutmargins 설정
LayoutMargins 설정탭 밑에 보면, "Spacing to nearest neighbor"이라 쓰여져있다. 여타 디자인 margin 속성과 동일한 기능을 수행하는 듯 하다.
임의의 값 집어넣고 Enter!
LayoutMargins 값이 적용되면?
위 그림과 같이 결과가 나온다.
1.4 Navigation Controller 생성
새로운 View Controller 추가(현재 목적과 다름, 일단 영상 흐름에 맞게 추가)
Table View가 있는 View Controller 위의 바에서 맨왼쪽 버튼(View Controller)를 누르면,
왼쪽 View Controller Scene 챕터에서 빨간 원 안의 View Controller 항목이 활성화됨을 확인할 수 있다.
이후, Navigation Controller에 선택된 View Controller를 포함시키는 작업이 진행된다.
Xcode toolbar -> editor -> Embed in -> Navigation Controller 클릭
위의 그림과 같은 결과가 나온다.
여기서 Navigation Controller와 연결하면, 어떤 의미가 있을까?
영상에도 나와있지만,
그림에 활성화돼있는 부분처럼 앱의 View에 뒤로가기, 추가, 삭제 등의 버튼 등이 위치하는 top navi bar가 생성된다. 이후, 페이지 이동, 데이터 추가, 삭제 기능 등에 활용될 것이다.
https://developer.apple.com/documentation/uikit/uinavigationcontroller
Apple Developer Documentation
developer.apple.com
2. TableView Outlet
2.1 ViewController.swift 이동
2.2 Class 전역 변수로 tableView 변수 추가
@IBOutlet var tableView: UITableView!
@IBOutlet이란 ?
: 간단하게 말하면, storyboard의 item과의 연결을 의미한다.
실제 Xcode에서 살펴보면
변수가 쓰여진 코드 줄 왼쪽편에 연결점이 나타난다. 앞으로의 과정에서 이 변수를 storyboard의 item과 연결시킬 것이다.
그를 통해 값을 가져오거나 변경시키는 것이 가능할 것이다.
@IBOutlet과 비슷한 맥락의 @IBAction이 있다. @IBOutlet은 아이템과 연결된 "변수", @IBAction은 아이템과 연결된 "동작, 이벤트"라고 이해하면 되겠다.
UITableView!란?
: Swift만의 독특한 문법이다. 기본적으로 변수, 상수를 선언할때,
[var, let] [이름]: (자료형) (!, ?)
이런 식으로 데이터가 선언된다.
하나하나 살펴보자
var과 let
var a = 10
let b = 10
var a = 100 // Ok
let b = 100 // error
// var: 변수, let: 상수를 의미하기 때문
먼저 Swift는 변수와 상수를 구분해서 선언할 수 있다.
var는 "변수", let: "상수"를 의미한다.
실제 컴파일러로 돌려보면, let으로 선언한 b가 constant(상수)라서 error가 발생하고 있다.
자료형(data type)
import Foundation
let a: Int = 10
let b: Double = 10
print(a) // 10
print(b) // 10.0
var c: Int = 10
var d: Double = 12.34
// c = d
// print(c) // error: cannot assign value of type 'Double' to type 'Int'
// d = c
// print(d) // error: cannot assign value of type 'Int' to type 'Double'
c = Int(d)
print(c) // 12
c = 10
d = 12.34
d = Double(c)
print(d) // 10.0
자료형은 [데이터 이름: 자료형]의 형태로 적는다는 것외에는 여타의 프로그래밍 언어 문법과 비슷하다.
* 추가적인 내용들이 있지만, 추후에 다루도록 함
!과 ?
이 !,?이 Swift 데이터 선언 문법에서 가장 중요하고, 핵심적인 내용이 아닐까싶다.
이에 대해 이해하기 위해서는 Swift이 "옵셔널"을 제공한다는 사실을 이해해야한다.
옵셔널이란 ?
"옵셔널(optional)"은 말그대로 "선택적인"이라는 말이다.
데이터 선언에서 "선택적"이라는 말은 "데이터가 있을수도 있고, 없을수도 있다"는 뜻이다.
그리고 동시에 데이터에 옵셔널 선언을 한 경우에만 "nil"을 활용할 수 있다.
코드를 통해 살펴보자
var a: Int
a = 10
print(a) // 10
// var b = nil // error: 'nil' requires a contextual type
// print(b)
var c: String? = "Hi"
print(c) // Optional("Hi")
c = nil
print(c) // nil
var d: Int? = 10
print(d) // Optional(10)
d = nil
print(d) // nil
var e: Int?
print(e) // nil
실제로 평범한 변수에 "nil"을 넣으니 error가 발생했다.
그리고 자료형 뒤에 "?"을 넣으니 "nil"을 넣어도 error가 발생하지 않고, 변수 값이 Optional()로 묶이는 것을 확인할 수 있었다.
이렇게 변수 값이 Optional()으로 묶이는 것을 "옵셔널 변수"이라고 부른다.
이 옵셔널 변수가 왜 필요할까?
: 옵셔널 기능이 추가되면, 개발을 하면서 옵셔널 변수가 아닌 변수들은 "자료형이 확실하고, 값이 분명히 들어있다"라는 확신을 가질 수 있다. 우리가 얼마나 NullpointException 등 변수의 값을 예상하지 못해 발생한 error들에 고생을 했던가. 그런 고생과 수고를 덜어주는 맥락의 swift만의 독특한 문법이라 하겠다.
이 옵셔널 변수 덕에 Swift는 관련 문법, 스킬들이 있다.
가볍게 살펴보고 넘어가자.
// 완전한 표현 방법
let a: Optional<Int> = nil
// 물음표으로 생략 가능
let b: Int? = nil
// Optional 추출
var c: String? = "Hello"
if c != nil {
print(c) // Optional("Hello")
print(c!) // Hello
} else {
print("nil")
}
// Optional 바인딩
var d: String? = "Hi"
if let d_ = d {
print(d_) // Hi
} else {
print("nil")
}
2.3 extension ViewController 코드 추가
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension ViewController: UITableViewDataSource {
// UITableViewDataSource 요구사항 추가 구현 필요
}
또다시 Swift의 특별한 문법 "extension"이다. 자바를 배운 나로서는 상속, 인터페이스개념과 비슷하게 다가왔다.
상속개념에서 바라보면,
ViewController에 UITableViewDelegate, UITableViewDataSource 상속받아 관련해 필요한 오버라이딩 메소드를 구현한다.
요런 느낌으로 받아들여진다.
실제로 비슷하다고는 하는데, 구체적으로 들여다보면 조금씩 차이가 있다. 관련한 포스팅을 아래 참조에 남기겠다.(* 야곰's blog)
그래서 UITableViewDelegate 프로토콜이 요구하는 func을 구현하는데, 내부의 코드가 있다.
tableView.deselectRow(at: indexPath, animated: true)
tableView내의 모든 cell을 선택해제하겠다는 메소드이다. 특정 cell 선택시 생기는 음영을 모두 해제한 상태로 만들겠다는 의미이다. 관련해서는 이후에 다시 확인할 수 있겠다.
2.4 storyboard item과 controller 변수 연결
2.4.1 main.storyboard 이동
2.4.2 storyboard의 ViewController 활성화 및 오른쪽클릭
storyboard의 장점 중에 하나가 등장한다.
그림처럼 ViewController를 활성화시키고, 오른쪽 클릭을 하면, Outlets항목에
ViewController에서 우리가 @IBOutlet으로 선언했던 tableView 변수가 보인다.
이 변수의 오른쪽에 보면, 우리가 controller에서 봤던 동그라미 빈칸이 보인다!
아하! 이걸 storyboard의 아이템이랑 연결하면 되는거구나! 연결해보자
드래그로 선을 이어 연결하면, 위 사진처럼 비어있던 동그라미 칸이 채워지는 걸 확인할 수 있다.
이렇게 연결되면 실제 app에서의 item의 상태, 속성, 값 등을 Controller로 가져와 확인, 수정, 삭제를 할 수 있게된다!
2.4.3 TableView의 cell 추가
연결된 TableView 활성화 -> 오른쪽의 Attributes inspector를 활성화
위의 그림처럼 inspector 보이지 않을시 상단 오른쪽 show inpectors 버튼 클릭
이후 Protoype cells 항목의 사이즈를 1로 올려주면 아래의 그림과 같이 Table View Cell이 추가된 것을 확인할 수 있다.
우리는 이 Table View Cell에 TodoList의 데이터 하나하나가 들어갈 것이다.
Table View Cell 활성화 -> identifier 설정
placeholder에 쓰여있듯, Todolist의 임의적인 추가, 제거에 따라, Table View Cell이 지속적으로 재사용할 수있도록 설정한다.
identifier를 "cell"로 간단하게 설정해서 필요할때마다 사용할 예정
2.4.4 ViewController.swift 이동
연결한 tableView에 대한 설정을 진행한다.
tableView의 cell들의 데이터를 담는 tasks라는 String 배열 추가
해당 tasks를 이용해 extension의 코드를 추가한다.
return tasks.count
위 코드는 tableview의 row의 section수를 정해주는 것이다.
의미는 다를 수 있지만, task와 cell이 서로 대응되기에 cell 하나하나를 각각 하나의 row section으로 나눠질 수 있게 돕는다.
예를 tasks 배열의 요소 개수가 5개라면 5개의 section으로 나눠줄 것이다.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = tasks[indexPath.row]
return cell
위 코드는 tasks 배열의 데이터를 cell의 textlabel의 text에 넣어주는 것이다.
위에서 말한대로 tasks의 요소 task와 cell을 대응시켰기에, 배열에 저장된 순서대로 cell에 데이터가 들어갈 것이다.
영상에서는 시뮬레이터를 돌려 확인하지만, xcode 업데이트 문제인지, 나는 시뮬레이터에서 보이지 않는다. 천천히 따라가다보면 확인할 수 있을것이다. 계속 해보자
2.5 "add" bar button 추가
2.5.1 main.storyboard로 이동
2.5.2 bar button item 추가
"bar button item"을 검색해서 위 navigation bar 위치에 추가한다.
* 드래그해서 추가하려 하면, 적절한 위치가 보여진다. 그 곳에 추가하면, 그림과 같이 추가된다.
2.5.3 bar button item "Title" 변경
위의 그림들처럼 추가된 bar button item을 더블클릭하면, title을 변경할 수 있게된다.
2.6 "add" bar button action 추가
2.6.1 ViewController.swift로 이동
2.6.2 @IBAction function 추가
@IBAction func didAddTap(){
print("didAddTap")
}
ViewController.swift에 @IBAction function을 추가했다.
위 코드의 print문은 확인을 위한 추가 코드이다.
예제 영상과 이외로 한번 @IBAction function의 동작을 확인해보려한다.
우리가 위에서 배운대로 storyboard에서 function 연결 후, 시뮬레이터를 돌려봤다.
시뮬레이터를 돌려보니 오른쪽 위에 우리가 추가했듯, Add버튼이 있었고, 클릭해보니 위 그림처럼 print문이 찍혔다.
이제 다시 영상으로 돌아가자
3. Adding To Do List Entry
3.1 새로운 ViewController 추가 및 연결
3.1.1 왼쪽 Project navigator에서 New File, (EntryController.swift, TaskController.swift)
위 그림과 같은 방식으로
"EntryController.swift", "TaskController.swift" 추가
3.2 EntryViewController.swift 작업
3.2.1 EntryViewController.swift로 이동
3.2.2 @IBOutlet인 UITextField 변수 추가
@IBOutlet var field:UITextField!
UITextField는 웹의 input type"text"와 같이 텍스트 입력창이다.
그러면 이후, storyboard에서 task의 값을 입력받기 위한 item 역시 추가될 것이다.
3.2.3 UITextFieldDelegate 프로토콜 채택
class EntryViewController: UIViewController, UITextFieldDelegate {
Swift에선 프로토콜을 상속받았다고 하지 않고, "프로토콜을 채택했다"고 표현한다.
- UITextFieldDelegate이란?
아래 참조로 애플 개발자문서를 추가했다.
개발자 문서에 따르면, UITextFieldDelegate는
"A set of optional methods to manage the editing and validation of text in a text field object."
"TextField Object에서 텍스트 편집 및 유효성 검사를 관리하는 일련의 Optional 메서드입니다."
라고 설명된다. 예제영상에서는 어떻게 활용하는지 살펴보자
3.2.4 UITextFieldDelegate 프로토콜 관련 코드 추가
override func viewDidLoad() {
super.viewDidLoad()
field.delegate = self
}
- "field.delegate = self"는 무슨 의미일까?
간단히 설명하지만, self(=현재 Class, Controller)에게 field를 "위임한다(delegate)"는 의미이다.
앞으로 field 변수, field와 연결된 item의 상태 변화 혹은 event 발생시 self(=현재 Class)가 작업을 대신 처리하겠다는 의미이다.
대신 작업을 처리? 그러면 작업을 처리하는 친구가 있어야겠다.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
saveTask()
return true
}
func saveTask(){
}
textFieldShouldReturn()은 UITextFieldDelegate 프로토콜에 정의된 함수이다.
이후 연결된 item에 이벤트 발생시 해당 함수가 실행되며, 필요한 작업이 진행될 것이다.
안의 코드를 보면 saveTask()가 있는데, textfield와 관련된 이벤트가 진행되면, task를 save하는 진행될 것이라는 걸 예상할 수 있다.
3.3 storyboard의 EntryView, TaskView 작업
3.3.1 main.storyboard로 이동
Main ViewController이외에 2개의 ViewController를 추가한 후, 이 ViewController identifier를 설정해주도록 하자.
identity inspector 챕터에 Class와 Storyboard ID를 설정해줄 것이다.
Class : EntryViewController
Storyboard ID : entry
Class : TaskViewController
Storyboard ID : task
위의 그림과 설명처럼 설정한다. 이는 storyboard의 임의의 item였던 친구들이 특정 컨트롤러 클래스와 연결되는 것을 의미한다. Storyboard ID는 이후 클래스에서 ViewController를 간략하게 불러오는데 사용될 것이다.
3.4 View Controller 이동 작업(didAddTap())
3.4.1 ViewController.swift로 이동
3.4.2 didAddTap function 수행 코드 작성
어플리케이션이라면 당연히 등장하는 페이지 이동, 컨트롤러 이동 코드이다.
관련 코드로 살펴보자
@IBAction func didAddTap(){
let vc = storyboard?.instantiateViewController(identifier: "entry") as! EntryViewController
vc.title = "New Task"
navigationController?.pushViewController(vc, animated: true)
}
storyboard는 UIStoryboard라는 내장 클래스의 객체를 호출한 것이다.
내장 클래스의 메소드인 "instantiateViewController"를 불러서 특정 ViewController 객체를 가져온다. 위 코드에서는 identifier가 "entry"인 ViewController를 객체로 왔다.
vc.title = "New Task" 를 통해 View의 Title을 설정하고,
navigationController?.pushViewController(vc, animated: true)를 통해 해당 ViewController 및 View로 이동을 한다.
navigationController 역시 "UINavigationController"라는 내장 클래스의 객체이다. 해당 클래스의 pushViewController 메소드를 이용해 해당 ViewController로 이동한다.
어라?연결을 하고 시뮬레이터를 돌려보니 오류가 발생한다.
fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
field 변수를 !으로 명시적으로 선언을 했는데, 값을 봤더니 nil값이 들어왔더라. 그래서 error가 냈다. 라고 컴퓨터가 말했다.
그래서 살펴봤더니 field가 storyboard의 아이템과 연결이 안돼있었다. 그래서 연결도 시켜주고, 대강 마진값도 설정해줬다.
수정해줬으니 다시 돌려보자
아직 너무 밋밋하지만, 페이지가 이동되고, 추가한 item이 보인다.
3.5 saveTask() 관련코드 작성
3.5.1 ViewController.swift로 이동 및 관련코드 작성(title, delegate)
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Tasks"
tableView.delegate = self
tableView.dataSource = self
}
위에서 작성한 것만 동일하기에 설명만 진행
현재 ViewController의 title을 "Tasks"로 설정.
extension을 통해 확장했던 tableView의 delegate, dataSource를 self(현재 ViewController)가 제어할 수 있도록 설정.
3.5.2 EntryViewController.swift로 이동
3.5.3 rightBarButton 추가
EntryViewController가 Task를 입력하고 추가할 수 있는 View로 사용될 수있도록 관련 설정을 진행한다.
코드로 살펴보자
override func viewDidLoad() {
super.viewDidLoad()
field.delegate = self
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done,
target:self, action: #selector(saveTask))
}
// func saveTask(){
//
// }
@objc func saveTask(){
}
viewDidLoad() 내부 코드를 먼저 보자!
viewDidLoad()에 rightBarButtonItem 관련 코드가 추가됐고, saveTask()에 @objc 어노테이션이 붙었다.
하나하나 살펴보자
navigationItem은 "UINavigationItem"이라는 내장 클래스이고, 그 중 rightBarButtonItem을 설정하고자 한다.
코드로 입력된 설정값을 설명해보면
UIBarButtonItem(title: "Save", style: .done, target:self, action: #selector(saveTask))
title: "Save" - title은 "Save"로 설정
style: .done - "done" button style로 설정
target: self - target을 현재 Controller로 설정 * 관련 정보는 참조로 붙임
action: #selector(saveTask) - "#selector()"는 변수를 참조하는 문법, target이 self이므로, action에 self.saveTask를 참조
위 설명과 같이 이해하면 좋겠다.
참조한 스택오버플로우를 보면, target, action은 아래 코드와 같이도 변경할 수 있다고 한다.
UIBarButtonItem(title: "Done", style: .done, methodToCall: self.saveTask)
다음은 saveTask function이다.
크게 달라진 점은 없고, "@objc" 어노테이션이 추가됐다.
- "@objc"는 무슨 의미?
검색해보면, "@objc"는 Object-C 문법을 사용할 수 있게하는 어노테이션이다.
"#selector()"가 Object-C 문법이라 활용하기 위해 추가한 코드로 이해하자.
3.5.4 saveTask() 코드 작성
@objc func saveTask(){
guard let text = field.text, !text.isEmpty else {
return
}
}
saveTask()는 function명에서 알 수 있듯이, "Save" 버튼을 누르면 task를 저장하는 기능을 수행하는 function이다. 그래서 saveTask()가 동작하면, 먼저 TextField인, field 변수의 text를 가져오도록 한다.
여기서 guard 문법이 튀어나온다.
- guard, 그리고 그 옆에 조건문은 뭐지?
"guard"는 조건문 개념이다.
: guard 조건 else {조건이 false일시 실행할 구문}
그런 맥락에서 위 코드를 분석해보면
1. let text = field.text - field.text의 값을 상수 "text"에 옮긴다.
2. "!text.isEmpty"가 "true(값이 있으면)"이면 그대로 진행.
3. "!text.isEmpty"가 "false(값이 없으면)"이면 "return(메소드 진행을 종료한다)"
라고 이해하면 되겠다.
field의 text 값을 가져왔으니 저장을 해야겠다! 가보자!
4. Saving UserDefaults
4.1 UserDefaults() 설정
4.1.1 ViewController.swift로 이동
4.1.2 UserDefaults() Setup
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Tasks"
tableView.delegate = self
tableView.dataSource = self
// Set up
if !UserDefaults().bool(forKey: "setup") {
UserDefaults().set(true, forKey: "setup")
UserDefaults().set(0, forKey: "count")
}
}
우리는 UserDefaults 클래스를 이용해 DB와 같이 값을 저장하고, 수정할 것이다.
- UserDefaults가 뭐야?
: UserDefaults클래스는 내장 클래스로서 float, int, bool, NSString 등 기본적인 자료형 데이터를 저장할 수 있다.
: 앱이 꺼진 이후에도 저정한 내용을 유지하고 있다.(간이 DB네..)
: Default Notification Center를 통해 값 변경 알림을 확인할 수 있다고 한다. (실제로 쓸지는 모르겠다)
위의 설명과 함께 작성된 코드를 살펴보자.
if !UserDefaults().bool(forKey: "setup") { }
UserDefaults().bool()은 저장된 데이터를 "bool"형태로 가져오는 메소드이다.
파라미터 "forKey"는 저장된 데이터의 키를 입력하는 파라미터로, 위 코드에선 "setup" 키에 해당하는 "bool"
데이터를 가져온다.
이해해보자면, 우리는 지금까지 작성한 코드에서 UserDefaults에 "setup"를 키로 갖는 데이터를 추가한 적이 없다. 그러니 당연히 false가 될 것이고, 내부 코드가 실행될 것이다.
처음 시작시, 실행될 내부 코드를 살펴보자
UserDefaults().set(true, forKey: "setup")
UserDefaults().set(0, forKey: "count")
UserDefaults().set()은 값을 입력, 추가하는 메소드이다.
그에 따라 위 코드를 바라보면,
첫번째 줄 : 키: "setup", 값: true을 추가한다.
두번째 줄 : 키: "count", 값: 0을 추가한다.
와 같이 분석할 수 있다.
그러면 왜 이 코드가 필요한지 알 수 있겠다.
위 코드는 앱이 최초에 수행될 때, 키 "count"의 데이터를 초기화하는 것이 목적이다.
"count"의 데이터는 앱에 새롭게 들어올 때마다 저장된 tasks의 count를 확인해 indexing할 수 있도록 도울 것이다.
이후의 활용을 기대해보겠다.
4.1.3 EntryViewController.swift로 이동
4.1.4 saveTask() 코드 추가
@objc func saveTask(){
guard let text = field.text, !text.isEmpty else {
return
}
guard let count = UserDefaults().value(forKey: "count") as? Int else {
return
}
let newCount = count + 1
UserDefaults().set(newCount, forKey: "count")
UserDefaults().set(text, forKey: "task_\(newCount)")
}
UserDefaults 클래스를 활용한 코드다.
분석해보자.
guard let count = UserDefaults().value(forKey: "count") as? Int else {
return
}
앞서 ViewController.swift에서 설정한 "count"의 값을 가져온다.
값이 확실히 있는지 확인하기 위해 guard를 활용한다.
해당 값이 "Int" 자료형 확인해서 아닐 경우 메소드를 종료한다.
let newCount = count + 1
UserDefaults().set(newCount, forKey: "count")
UserDefaults().set(text, forKey: "task_\(newCount)")
새로운 task를 넣기 위해 count+1인, newCount 선언
: "count"의 값을 newCount로 갱신한다.
: "task_\(newCount)"에 현재 field의 값 text를 저장한다.
이와 같은 코드를 통해 바라보면, 이후 저장된 모든 tasks들을 불러올 때,
count: 5라면, for문을 통해 task_1, task_2, task_3, task_4, task_5로 불러오면 되겠다.
5. Passing Data with Closure
5.1 ViewController에서 활용한 Closure 추가
Save한 Task가 ViewController에서 바로 보여질수 있도록 작업을 할 것이다.
EntryViewController에 Closure 변수를 추가하여, ViewController에서 활용하도록 하겠다.
추가된 코드로 살펴보자.
- EntryViewController.swift
var update: (() -> Void)?
@objc func saveTask(){
update?()
navigationController?.popViewController(animated: true)
}
- ViewController.swift
func updateTasks() {
}
@IBAction func didAddTap(){
vc.update = {
DispatchQueue.main.async {
self.updateTasks()
}
}
}
EntryViewController에 "update"라는 클로져 변수를 선언한다. 그리고, saveTask()에서 UserDefaults()에 데이터를 추가한 이후에 "update?()"로 메소드를 실행하기도 한다. 그렇게 하면, ViewController의 vc.update 내부코드가 실행된다.
EntryViewControllder의 update?() -> ViewController의 vc.update 실행
- DispatchQueue.main.async : 내부 코드가 비동기적으로 실행시키는 함수
- self.updateTasks() : 아직 내부 코드는 없으나, 추가 및 수정된 task가 있기에, tableview를 갱신하기 위한 메소드이다.
작성 코드가 작동하는지 확인해보자
시뮬레이터를 돌려 확인해보니 위 사진처럼, TextField에 입력도 되고, 클릭시 기존 ViewController로 돌아가지만, 저장된 task가 보이지 않는다. 저장된 task를 View에 올려보자!
6. Getting Saved Data
6.1 updateTasks() 코드 작성
override func viewDidLoad() {
// ...
updateTasks()
}
func updateTasks() {
guard let count = UserDefaults().value(forKey: "count") as? Int else{
return
}
for x in 0..<count {
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String {
tasks.append(task)
}
}
}
task 추가, 저장 후 클로져변수 update를 통해 "updateTask()" 메소드가 실행된다.
그래서 일단, UserDefaults()에 저장된 값들을 가져와 ViewController의 "tasks" 배열에 추가했다.
그렇다고 해서 View에 올려지지는 않는다.
tableView에 이미 빈 cell들로 가득 차있기 때문이다.
App 시작때도 바로 데이터가 보여질 수 있도록 viewDidLoad() 내부에 updateTask()를 추가했다.
6.2 updateTasks() 코드 추가
func updateTasks() {
tasks.removeAll()
guard let count = UserDefaults().value(forKey: "count") as? Int else{
return
}
for x in 0..<count {
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String {
tasks.append(task)
}
}
tableView.reloadData()
}
- 추가된 코드
tasks.removeAll() : EntryController에서 UserDefaults()에 값을 추가했다고 하지만, tasks에도 추가된 것은 아니다. 그리고, 추가 뿐만 아니라 제거일 경우도 있기때문에, update의 경우에는 tasks를 전부 비우고, 다시 채우는 것이 좋겠다.
tableView.reloadData() : updateTask() 내부 코드로 tableView와 연결된 tasks의 데이터가 갱신됐다. 그렇기에 tableView 역시 갱신해서 그려줘야한다. 그 역할을 수행하는 코드이다.
어떤 결과일지 바로 봐보자!
오호! 드디어 값이 등장했다. 추가하고 확인하고 추가하고 확인하고ㅋㅋ
동작한다!!
근데 좀 부족하다. 삭제도 되고, 디테일 창으로도 가보고 싶다! 언능 다음챕터로
7. Opening Task Details
7.1 cell click event 추가
예제에서는 task이 담긴 cell을 눌렀을 때 detail창으로 이동하도록 만들고자 한다.
관련코드를 따라가보자
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let vc = storyboard?.instantiateViewController(identifier: "task") as! TaskViewController
vc.title = "Task"
navigationController?.pushViewController(vc, animated: true)
}
}
UITableViewDelegate를 채택한 extension의 내부 메소드에 didAddTap() 메소드의 vc 코드를 가져와 이번엔 TaskViewController의 객체를 만들었다.
추가된 코드를 통해
1. TaskViewController의 title 설정
2. tableView의 "cell"을 클릭할 때 TaskViewCotroller로 이동
이 두 가지를 정의했다.
7.2 TaskViewController.swift로 이동
7.3 task detail을 위한 item 추가
@IBOutlet var label:UILabel!
var task:String?
override func viewDidLoad() {
super.viewDidLoad()
label.text = task
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Delete", style: .done, target: self, action: #selector(deleteTask))
}
@objc func deleteTask(){
}
이번에도 기본적인 구조를 그려놓고, 채워나갈 것이다.
기본적인 구조
1. View에 그려질 "label" 변수를 추가하고,
2. task가 label.text가 되도록 할 것이며,
3. "Delete" rightBarButtonItem을 추가할 것이며,(이미 해결)
4. 버튼 클릭시 "deleteTask()" 메소드를 통해 해당 task를 삭제할 것이다.
이 목표를 하나하나 해결해 나가보자.
*deleteTask 구현하려하지만, 시간 부족으로 예제 동영상에서는 구현하지 않았습니다.*
*아래 포스팅에 개인적으로 구현한 내용 추가할 예정입니다.*
7.4 label 변수와 storyboard item 연결
- main.storyboard로 이동, label 추가, layout margin 설정
- label 변수와 label 아이템 연결
- ViewController의 task, TaskViewController로 가져오기
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let vc = storyboard?.instantiateViewController(identifier: "task") as! TaskViewController
vc.title = "Task"
vc.task = tasks[indexPath.row] // TaskViewController로 클릭한 task data 전송
navigationController?.pushViewController(vc, animated: true)
}
}
추가된 "vc.task = tasks[indexPath.row]"를 통해 클릭한 task의 data를 TaskViewController로 이동시킨다.
시뮬레이터로 결과를 확인해보자
이전과 다르게 task를 클릭하면, 다른 View로 이동해 클릭한 task의 text가 그려졌다!
오른쪽 위에 "Delete" 버튼도 있지만, 눌러도 작동하지 않는다.
예제는 이렇게 끝이 납니다!
Todolist 뭐 별거 있어하고 포스팅하다보니 생각보다 복잡하고, 깊게 들여다보지 않았으면 몰랐을 내용들을 많이 알게됐다. 중구난방 되는대로 썼지만, 누군가에겐 도움이 되길 바랍니다..ㅋㅋ
추가로 Task Delete 기능 추가하고 마무리하겠습니다!
- ViewController.swift
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let vc = storyboard?.instantiateViewController(identifier: "task") as! TaskViewController
vc.title = "Task"
vc.task = tasks[indexPath.row]
vc.index = indexPath.row
vc.update = {
DispatchQueue.main.async {
self.updateTasks()
}
}
navigationController?.pushViewController(vc, animated: true)
}
}
- TaskViewController.swift
import UIKit
class EntryViewController: UIViewController, UITextFieldDelegate {
@IBOutlet var field:UITextField!
var update: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
field.delegate = self
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target:self, action: #selector(saveTask))
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
saveTask()
return true
}
@objc func saveTask(){
guard let text = field.text, !text.isEmpty else {
return
}
guard let count = UserDefaults().value(forKey: "count") as? Int else {
return
}
let newCount = count + 1
UserDefaults().set(newCount, forKey: "count")
UserDefaults().set(text, forKey: "task_\(newCount)")
update?()
navigationController?.popViewController(animated: true)
}
}
- 결과물
* 참조
- http://online.swiftplayground.run/
Swift - 프로토콜, 익스텐션 - yagom's blog
yagom's blog Swift - 프로토콜, 익스텐션
blog.yagom.net
- https://developer.apple.com/documentation/uikit/uitextfielddelegate
Apple Developer Documentation
developer.apple.com
What is the difference between Target and Action in swift?
When should I work with Target and nil action? On the other hand, when should I work with Action and nil Target and when should I work with both Action and Target? let rightButton = UIBarButtonItem(
stackoverflow.com