포스트

Todoey (9)

SuperClass로 추가 기능 관리.

지금껏 Readme파일의 내용을 가져와서 우리의 코드에 맞게 수정하는 작업을 했다.

그렇게 하지말고, 새로운 Class하나 만들어서, VC가 해당 Class를 상속하게 해서 관리를 해보도록 하자.

1. 새로운 파일 생성 및 내부 코드 수정

지금 Category, TodoList VC 모두 UITableViewController 이므로 새로운 파일도 UITableViewController로 만들어준다.

그리고 파일명은 SwipeTableViewController 이렇게 해주었다.

이제 이파일이 SuperClass가 될 것이다.

이제 CategoryVC에 작성했던 extension의 내용을 전부 SwipeTableVC로 옮겨준다.

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
32
33
34
35
36
37
38
39
40
41
42
43
import UIKit
import SwipeCellKit

class SwipeTableViewController: UITableViewController, SwipeTableViewCellDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()


    }

    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
        
        guard orientation == .right else { return nil }
        
        let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
            
            if let currentCategory = self.categories?[indexPath.row] {
                do {
                    try self.realm.write {
                        self.realm.delete(currentCategory)
                    }
                } catch {
                    print("Error \(error.localizedDescription)")
                }
            }
            
        }
        
        // customize the action appearance
        deleteAction.image = UIImage(named: "delete-icon")
        
        return [deleteAction]
    }
    
    func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
        var options = SwipeOptions()
        options.expansionStyle = .destructive
        return options
    }

}

하지만 이제 문제가 생긴다.

CleanShot 2024-04-15 at 19 46 25@2x

바로 여기부분이다.

아무래도 그대로 cut & paste를 하다보니 생긴 문제이기도 하고, SwipeTableVC의 경우 SuperClass이기에, 하위 Class에 대한 내용을 알 필요가 없기도 하다.

무슨말이냐면, Category, TodoListVC가 SwipeTableVC를 상속을 받기에, SwipeTableVC 안에는 그 내용이 담길 필요도, 이유도 없다는것.

2. Category VC 수정

class CategoryViewController: UITableViewController 현재 CategoryVC는 UITableViewController를 상속 받고 있다.

이제 이 상속을 지우고, SwipeTableViewController로 대신 상속받게 할것이다.

CleanShot 2024-04-15 at 19 50 05@2x

import SwipeCellKit을 지우자마자 발생하는 에러.

SwipeTableVC에 TableView Datasource Methods 구현이 필요해졌다.

다시 SwipeTableVC로 돌아가서 TableView Datasource Methods 부분을 구현해보자.

3. SwipeTableVC TableView Datasource Methods 구현.

1
2
3
4
5
6
7
8
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as! SwipeTableViewCell
        
        cell.textLabel?.text = categories?[indexPath.row].name ?? "No Categories added Yet"
        cell.delegate = self
        
        return cell
    }

우선은 CategoryVC의 cellForRowAt 함수내 cell을 담당하던 코드를 넣어주자.

SwipeTableVC가 SuperClass의 성격을 가지므로, let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell")의 부분에서 identifier의 문자열을 좀 더 General하게 해주는것이 좋기에 "CategoryCell"→"Cell"로 고쳐주자.

그리고 StoryBoard의 Cell의 Identifier도 모두 고쳐주자.

CleanShot 2024-04-15 at 19 57 29@2x

cell.textLabel?.text = categories?[indexPath.row].name ?? "No Categories added Yet" 이 부분 역시

현재 CategoryCell 특정이기에 지워버리자.

4. CategoryVC에서 cell 상속.

1
2
3
4
5
// before
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as! SwipeTableViewCell

// after
let cell = super.tableView(tableView, cellForRowAt: indexPath)

super를 통해 SwipeTableVC의 cellForRowAt의 cell을 그대로 사용한다.

실행하니 드래그는 되는데, 실제로 삭제를 하려고하니 에러가 발생했다.

1
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

생각보다 에러가 꽤나 길다.

그전에 SwipeTableVC에 delete에 관한 메서드를 주석을 달았기 때문이다.

5. 삭제 함수 구현하기

이제 SwipeTableVC에 함수를 구현해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
        
        guard orientation == .right else { return nil }
        
        let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
            
            self.updateModel(at: indexPath) // new

        }
        
        // customize the action appearance
        deleteAction.image = UIImage(named: "delete-icon")
        
        return [deleteAction]
    }
    
func updateModel(at indexPath: IndexPath) { // new
    // Update our data Model.
}

현재 데이터를 갱신해주는 함수를 구현했다.

CategoryVC로 돌아와서,

1
2
3
4
5
// MARK: - Delete Data From Swipe
    
override func updateModel(at indexPath: IndexPath) {

}

updateModel을 재정의 (override) 해서 사용할 것이다.

왜 갑자기 override를 하는지?

CategoryVC 와 SwipeTableVC는 상속 관게이다.

SwipeTableVC에서 updateModel 함수를 구현 해뒀으니. 상속받는 CategoryVC는 동일한 이름, parameter의 함수를 사용하기 위해선 재정의를 해야하는것.

ViewDidload도 override인 이유는 UIViewController를 상속받았기에 가능.

여기에 아까 삭제에 관해 주석쳤던 부분을 넣어주자.

1
2
3
4
5
6
7
8
9
10
11
12
// MARK: - Delete Data From Swipe
override func updateModel(at indexPath: IndexPath) {
        if let currentCategory = self.categories?[indexPath.row] {
            do {
                try self.realm.write {
                    self.realm.delete(currentCategory)
                }
            } catch {
                print("Error \(error.localizedDescription)")
            }
        }
    }

그럼 여기서 누구나 가질 수 있는 궁금증

SwipeTableVC의 updateModel에 내가 기능을 구현한 건 사용을 할 수 없다?

정답은 X 만약 override을 하여 해당 함수 자체를 재정의 했지만, 상속받는 함수의 기능도 사용을 하고 싶다면?

super.updateModel 이렇게 적으면 된다 (현재 함수를 예시로)

이해를 돕기위해 아래 예시를 하나 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SwipeTableVC
func updateModel(at indexPath: IndexPath) {
        print("Status Updated") // new
    }

// CategoryVC
override func updateModel(at indexPath: IndexPath) {
        
        super.updateModel(at: indexPath) // new
        
        if let currentCategory = self.categories?[indexPath.row] {
            do {
                try self.realm.write {
                    self.realm.delete(currentCategory)
                }
            } catch {
                print("Error \(error.localizedDescription)")
            }
        }
    }

super를 사용함으로써 갖는 의미는 내가 이 함수를 상속 받았지만 내가 기능을 다시 정하지만, 상속받는 기능도 사용하겠다! 는 의미. super를 사용하지 않으면 나는 함수를 상속 받으면서 기능은 내가 다시 정하겠다 는 의미.

실행해보자.

Apr-16-2024 08-51-32

굿.

그러면 또 갑자기 이런 생각이 들수도 있다.

아까 4번에서는

1
2
3
4
5
// before
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as! SwipeTableViewCell

// after
let cell = super.tableView(tableView, cellForRowAt: indexPath)

위에는 super를 썼는데 함수처럼 쓰인게 아니라 변수안에 들어갔네요?

tableView의 함수가

1
2
3
4
5
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! SwipeTableViewCell
       cell.delegate = self     
       return cell
   }

마지막에 cell을 리턴하기에 가능 했던것.

즉 after에서 나는(CategoryVC) cell을 재정의해서 쓰는것이 아니라, 셀은 그냥 상속받는거 쓸게. 의 의미.

끝.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.