[231222] Today I Learned

YoungHyun Kim·2023년 12월 22일
1

TIL ✍️

목록 보기
24/68

본 프로젝트는 스파르타 코딩클럽 iOS 앱개발 부트캠프 과정 중 개인 과제로 작성된 할 일 관리 어플 프로젝트입니다. 모든 프로젝트 파일들은 제 깃허브에 업로드되어 있습니다.

코드 리뷰 및 해설

1. MainView에 해당하는 TodolistViewController.swift

import UIKit

struct Todo {
    var todoTitle: String
    var todoContents: String
    var isCompleted: Bool
}


class TodoListViewController: UIViewController {

    // 변수 선언 부분
    @IBOutlet var ToDoTableView: UITableView!
    static var todoList: [Todo] = [Todo(todoTitle: "11", todoContents: "11111", isCompleted: true)]
    static var todoTitleInDetail = String()
    static var todoContentsInDetail = String()
    static var tempIsCompleted: Bool?
    
    // 함수 선언 부분
    
    override func viewDidLoad() {
        super.viewDidLoad()
        ToDoTableView.delegate = self
        ToDoTableView.dataSource = self
        ToDoTableView.reloadData()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        ToDoTableView.reloadData()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        ToDoTableView.reloadData()
    }
}

extension TodoListViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return TodoListViewController.todoList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = ToDoTableView.dequeueReusableCell(withIdentifier: "ToDoCell", for: indexPath) as! ToDoTableViewCell
        
        cell.setCell(TodoListViewController.todoList[indexPath.row])
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        TodoListViewController.todoTitleInDetail = TodoListViewController.todoList[indexPath.row].todoTitle
        TodoListViewController.todoContentsInDetail = TodoListViewController.todoList[indexPath.row].todoContents
        
        if let ic = TodoListViewController.tempIsCompleted {
            TodoListViewController.todoList[indexPath.row].isCompleted = ic
        }
        print(TodoListViewController.todoList[indexPath.row].isCompleted)
    }
    
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        return .delete
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            tableView.beginUpdates()
            TodoListViewController.todoList.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
            tableView.endUpdates()
        }
    }
}
  1. 앞으로 추가할 할 일에 대한 데이터를 정리할 구조체 Todo를 선언해줬다.
  2. TodoListViewController에서 관리할 TableView의 outlet 변수와, 할 일을 탭했을 때 진입할 수 있는 뷰를 제어하는 DetailViewController에 전해질 임시 변수들을 선언해줬다.
  3. viewDidLoad(), viewWillAppear(), viewWillDisappear() 함수 내에 데이터를 가지고있는 TodoTableView의 역할을 위임, 데이터 관리 권한을 넘겨주는 구문을 작성했다. 또 TableView의 데이터를 다시 로드하는 구문들도 적어줬다.
  4. 가시성을 위해서, UITableViewDelegate, UITableViewDataSource 프로토콜에 필히 작성해야 하는 함수들은 아래쪽에 선언했다.
  5. tableView 함수에 대해서
    5-1. 매개인자로 numberOfRowsInSection을 갖는 tableView함수는 데이터 모델의 갯수만큼의 TableViewCell을 만들어주는 역할을 한다.
    5-2. 매개인자로 cellForRowAt을 갖는 함수는 cell 객체를 생성하고 내부에 데이터를 적절히 적용해준다.
    5-3. 매개인자로 didSelectRowAt을 갖는 함수는, cell 내부의 UILabel을 터치했을 때, DetailView로 진입할 수 있도록 하는 함수이다.
    5-4. 매개인자로 editingStylrForRowAt, editingStyle 을 갖는 두 함수는, cell을 삭제하는 방식과 과정을 결정한다.

2. TableView 내부의 Cell을 관리하는 TodoTableviewCell.swift

import UIKit

class TodoTableViewCell: UITableViewCell {

    @IBOutlet weak var todoTableTitle: UILabel!
    var todoTableContents: String!
    @IBOutlet weak var isCompletedSwitch: UISwitch!
    
    var todo: Todo?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        isCompletedSwitch.setOn(false, animated: true)
        automaticallyUpdatesContentConfiguration = true
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
    @IBAction func isCompletedChanged(_ sender: Any) {
        guard let todo else { return }
        if isCompletedSwitch.isOn {
            todoTableTitle?.attributedText = todo.todoTitle.strikeThrough()
        } else {
            todoTableTitle?.attributedText = todo.todoTitle.nonStrikeThrough()
        }
        TodoListViewController.tempIsCompleted = isCompletedSwitch.isOn
    }
    
    func setCell(_ _todo: Todo) {
        todo = _todo
        guard var todo else { return }
        if todo.isCompleted  {
            todoTableTitle?.attributedText = todo.todoTitle.strikeThrough()
            todo.isCompleted = true
        } else {
            todoTableTitle?.attributedText = todo.todoTitle.nonStrikeThrough()
            todo.isCompleted = false
        }
        isCompletedSwitch.isOn = todo.isCompleted
    }
}
  1. cell 내부에 위치한 UILabel, UISwitch의 outlet 변수들과, TodoListViewController 내부의 데이터를 받기 위한 Todo 타입의 todo 변수를 선언했다.
  2. Xib 형태의 스토리보드 파일이 컴파일되면 실행되는 awakeFromNib 함수 내부에는 cell 내부의 contents들을 자동으로 업데이트해주는 클래스 변수를 true로 바꿔주었다. (필요한지 모르겠음)
  3. UISwitch를 작동시키면 Todo 내부의 isCompleted에 변화를 가해주며 UILabel에 취소선을 적용시키는 작동을 하는 함수 isCompletedChanged() 함수를 작성해줬다.
  4. setCell함수는 TodoListViewController 내부에서 사용할 함수로써, Todo.isCompleted의 상태에 따라서 취소선 구현을 결정하는 함수이다.

3. 할 일을 탭했을 때 접근할 수 있는 DetailView를 제어하는 DetailViewController.swift

import UIKit

class DetailViewController: UIViewController {

    @IBOutlet weak var todoTitleLabelinDetail: UILabel!
    @IBOutlet weak var todoContentsLabelinDetail: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        todoTitleLabelinDetail.text = TodoListViewController.todoTitleInDetail
        todoContentsLabelinDetail.text = TodoListViewController.todoContentsInDetail
        todoTitleLabelinDetail.sizeToFit()
        todoContentsLabelinDetail.sizeToFit()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        todoTitleLabelinDetail.text = ""
        todoContentsLabelinDetail.text = ""
    }
}
  1. TodoListViewController 에서 선언했던 임시 변수들을 DetailView 내의 UILabel 들에 적용할 수 있도록 하는 구문들을 viewDidLoad 함수 내에 작성해뒀다.
  2. DetailView를 빠져나갈 때, UILabel들의 내용을 비울 수 있도록 viewDidDisappear 함수 내에 해당 기능을 할 수 있는 구문을 작성했다.

4. 할 일들을 추가할 수 있는 View를 정의하는 AddTodoViewController.swift

import UIKit

class AddTodoViewController: UIViewController {

    @IBOutlet weak var todoContentsTextfield: UITextField!
    @IBOutlet weak var todoTitleTextfield: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    @IBAction func didAddToDoButtonTapped(_ sender: Any) {
        
        // 제목이나 내용 TextField가 비었을 때 나올 Alert 컨트롤러 객체 생성
        let missingTitleAlert = UIAlertController(title: "내용이 모두 입력되지 않았습니다.", message: "빈 칸이 있는지 확인하십시오.", preferredStyle: UIAlertController.Style.alert)
        // 정말 추가할 것인지 확인할 때 나올 Alert 컨트롤러 객체 생성
        let sureToAddAlert = UIAlertController(title: "할 일 추가", message: "위 내용을 추가하시겠습니까?", preferredStyle: UIAlertController.Style.alert)
        
        // Alert 액션 객체 생성
        let addAction = UIAlertAction(title: "추가", style: UIAlertAction.Style.default){ _ in
            TodoListViewController.todoList.append(Todo(todoTitle: self.todoTitleTextfield.text!, todoContents: self.todoContentsTextfield.text! ,isCompleted: false))
            self.todoTitleTextfield.text = ""
            self.todoContentsTextfield.text = ""
            
            self.navigationController?.popViewController(animated: true)
        }
        
        let confirmAction = UIAlertAction(title: "확인", style: UIAlertAction.Style.default)
        let cancelAction = UIAlertAction(title: "취소", style: UIAlertAction.Style.cancel)
        
        // Alert 컨트롤러에 버튼 추가
        missingTitleAlert.addAction(confirmAction)
        sureToAddAlert.addAction(addAction)
        sureToAddAlert.addAction(cancelAction)
        
        if self.todoTitleTextfield.text == "" || self.todoContentsTextfield.text == "" {
            self.present(missingTitleAlert, animated: true)
        } else {
            self.present(sureToAddAlert, animated: true)
        }
    }
}
  1. 할 일의 제목과 내용을 적을 수 있는 두 개의 UITextField에 대한 outlet 변수를 선언했다.
  2. 화면의 중앙쯤에 나오는 "추가" 버튼을 눌렀을 때의 동작을 didAddTodoButtonTapped 함수 내부에 정의했다.
    2-1. 할 일을 추가하도록 해주는 "추가" 버튼을 눌렀을 때의 동작을 정의해줬다.
    할 일의 제목이나 내용을 적지 않고 "추가" 버튼을 눌렀을 때 나올 알림창의 역할을 해줄 missingTitleAlert 라는 이름의 UIAlertController 객체를 생성해준다.
    제목이나 내용을 누락할 시에는 목록에 추가되지 않게 할 생각이니, 해당 알림창에는 입력창으로 다시 되돌아갈 수 있는 "확인" 버튼만을 추가하도록 한다.
    2-2. 제목과 내용을 모두 입력한 후 "추가"버튼을 눌렀을 때, 정말 할 일 목록에 추가할 것인지 묻는 이름이 sureToAddAlertUIAlertController 객체를 생성한다.
    "추가"라는 버튼을 누르게되면, TodoListViewController 내부의 데이터 모델인 todoList 배열에 TextField 내의 데이터들이 입력되도록 하는 구문이 실행된다.
    "취소"라는 버튼을 누르면 이전에 입력을 진행하던 창으로 다시 되돌아가도록 UIAlertAction 객체들을 구성했다.

프로젝트 진행하며 느낀 점

  1. 지금까지 Command Line 프로젝트를 생성해서 알고리즘 문제나 프로젝트라고 하기도 뭐한 프로젝트들을 만들어대다가 처음으로 화면도 구현해보는 좋은 경험이 되었다.
  2. ViewController 등의 의미를 어느정도는 알게 된 것 같다. 지금까지는 Storyboard 상의 화면 = ViewController 정도의 느낌이긴 하지만... 더 공부하면 정확히 감을 잡을 수 있지 싶다.
  3. ViewController 간의 데이터 전달 및 처리를 할 때, 생각보다 간단한 처리라고 생각하는 작업도 내 기준 복잡한 처리를 요하는 경우가 많았다.
  4. 책으로만 공부했던 디자인 패턴을 어떻게 구현하는지 잘 생각해보고, 익숙하게 코드를 짤 수 있어야 할 것 같다고 느꼈다. 앞으로 여러 사람들의 코드 리뷰를 하며 어떤 구조인지 생각해 보는 것도 좋을 것 같다.
profile
iOS 개발자가 되고 싶어요

0개의 댓글