포스트

10주차 과제 (7)

현재 디자인도 개판이고, 전체 삭제, 추가버튼도 이제 실행이 되게 해야한다.

전체삭제는 말 그대로 그냥 다 지우면 되고, 추가버튼은 화면전환을 한뒤 서치바 활성이다.

그리고 SwipeAction을 통해 부분 삭제를 가능하게 해야한다.

여기까지 구현을 하고서, level5 무한스크롤 까지 끝내고 과제의 요구사항 및 선택적으로 있는 추가기능도 확인을 하면 될 것 같다

디자인 수정

현재 Layout 대충 잡고 기능부터 구현해서 개판이 난 상태이다, 이부분을 먼저 수습하고 가는게 좋을 듯 하다.

우선 버튼의 title이 보이지 않던 문제 수정

1
2
3
4
5
6
7
private let deleteAllButton: UIButton = {
        let button = UIButton()
        button.setTitle("전체 삭제", for: .normal) // added
        button.setTitleColor(.red, for: .normal) // added
        button.backgroundColor = .white
        return button
    }()

아주 멍청하게 titleLabel이라고 생각하고 해버렸던게 문제였다.

1
2
3
4
5
6
7
8
9
10
private lazy var hStackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [
        deleteAllButton,
        title,
        addButton
        ])
        stackView.axis = .horizontal 
        stackView.distribution = .fillEqually // added
        return stackView
    }()

그리고 StackView역시 아무 생각없이 영어 그대로의 의미를 생각해서 alignment property에 접근했던게 문제,

Simulator Screenshot - iPhone 15 Pro - 2024-05-08 at 09 32 25

구색은 완료.

버튼 작동 확인을 위해 tabpublisher 사용하여 print를 사용해 콘솔에 출력을 해본다.

1
2
3
 button.tapPublisher.sink { [unowned self] _ in
            print("삭제")
        }.store(in: &cancellables)

확인완료. 버튼으로써의 구색은 갖춰졌다.

추가 기능 구현

해당기능을 클릭하면 화면전환을 하고 searchbar를 활성시키면 된다.

단순히 tabbarvc의 인스턴스를 호출하고 인덱스를 선택하게 하려 했는데 되지 않아서 검색을 했고 스택오버플로우에 나와 같은 고민을 했던 글이 있어 가져왔다. 적용해보니 바로된다.

1
2
3
if let tabBarController = self.window!.rootViewController as? UITabBarController {
                    tabBarController.selectedIndex = 0
                }

포인트는 rootvc가 tabbar라면 화면을 전환시키라는것이다. 또 하나 배워간다.

이제 서치바를 활성 시키면 된다.

어제 스크럼 중 팀원 한분이 이부분에 대해 문제를 겪고 계셨는데 왜 그런지 이해가 간다.

1
2
3
4
5
if tabBarController.selectedViewController == MainViewController() {
                    print ("yes")
                } else {
                    print(tabBarController.selectedViewController)
                }

우선 이렇게 출력을 했는데 현재 선택된 vc가 NavigationController라고 되어있어서

SceneDelegate의 firstVC = UINavigationController(rootViewController: rootVC) 이부분을 삭제한다.

이후 코드를 다음과 같이 적어주었다.

1
2
3
4
5
if let tabBarController = self.window!.rootViewController as? UITabBarController {
                tabBarController.selectedIndex = 0
                let current = tabBarController.selectedViewController as? MainViewController
                current?.searchView.searchBar.becomeFirstResponder()
            }

May-08-2024 10-18-13

성공.

키보드 다운 구현

서치바를 탭하면 내가 입력하기 전까지 키보드가 내려가지 않는다.

이부분을 좀 수졍해얄 필요를 느껴 수정해보기로 한다.

1
2
3
4
bar.showsCancelButton = true
bar.cancelButtonClickedPublisher.sink { [unowned self] _ in
            endEditing(true)
}.store(in: &cancellables)

단순하게

May-08-2024 11-40-54

캔슬버튼 활성화로 퉁.

cell swipeaction을 통한 삭제

전체삭제를 구현 하기 전, 먼저 swipe action으로 삭제를 하려고한다.

1
2
3
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
    }

이녀석은 leading, trailing을 이용하여 원하는 방향에서 swipe가 가능하게 설정이 가능.

이부분은 자세한 부분이 기억나지 않아 이전에 작성했던 코드를 참고해서 쓴다.

작성했던 코드를 보니 우선적으로 해야할건 contextualAction을 등록하는것이다.

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
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        let deleteButton = UIContextualAction(style: .normal, title: "삭제") { (UIContextualAction, UIView, success: @escaping (Bool) -> Void)  in
            let alert = UIAlertController(title: "삭제하기", message: "정말 삭제하실 건가요?", preferredStyle: .alert)
            
            let ok = UIAlertAction(title: "OK", style: .destructive, handler: { [unowned self] _ in
                
                wishVM.deleteSpeificData(selectedCell: wishVM.wishDocument[indexPath.row])
                tableView.beginUpdates()
                wishVM.wishDocument.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: .fade)
                tableView.endUpdates()
            })
            
            let cancel = UIAlertAction(title: "취소", style: .default)
            
            alert.addAction(ok)
            alert.addAction(cancel)
            self.present(alert,animated: false)
            
            success(true)
        }
        deleteButton.backgroundColor = .red
        return UISwipeActionsConfiguration(actions: [deleteButton])
    }

Vm작성

1
2
3
4
5
6
7
8
9
func deleteSpeificData (selectedCell: NSManagedObject) {
        do {
            try context.delete(selectedCell)
            try context.save()
        } catch {
            
        }
        
    }

May-08-2024 12-05-26

swipeaction 하도 안썼더니 가물가물 했다.

이번에 좀 기억을 다시 해두는 편이 좋을듯하다.

DB에서도 삭제가 확인 되었다.

전체삭제

VM에서 모델링을 하고 바로 호출을 하면 될것같다.

생각해보니 한번도 전체 다 지운적이 없는듯 하여 검색을 해보고 그걸 토대로 구현한다.

context에서 delete부분만 조금 다듬으면 될것같긴하다.

스택오버플로우에 나와 같은 고민이 있었고 채택율이 가장 높은 글이 있어 그걸 적용해 보려한다.

1
2
3
4
5
6
7
8
9
10
11
func deletaAllData () {
        let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WishListModel")
        let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
        
        do {
            try context.execute(deleteRequest)
            try context.save()
        } catch {
            
        }
    }

let request: NSFetchRequest<WishListModel> = WishListModel.fetchRequest() 내가 한 리퀘스트와 거의 같지만 타입이 달랐다.

CleanShot 2024-05-08 at 13 34 56@2x CleanShot 2024-05-08 at 13 35 24@2x

둘은

같은걸 출력할지 몰라도 타입은 다르다.

May-08-2024 13-45-50

삭제는 확인 완료.

지금은 버튼에 삭제만 해두었기에,

코드를 좀더 보강한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 private lazy var deleteAllButton: UIButton = {
        let button = UIButton()
        button.setTitle("전체 삭제", for: .normal)
        button.setTitleColor(.red, for: .normal)
        button.backgroundColor = .white
        button.tapPublisher.sink { [unowned self] _ in
            if let vc = childViewController as? WishlistViewController {
                let alert = UIAlertController(title: "삭제하시겠습니까?", message: "삭제하시면 복원은 불가능합니다.", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "확인", style: .destructive, handler: { [unowned self] _ in
                    vc.wishVM.deleteAllData()
                    vc.wishVM.getDocumentfromCoreData()
                    vc.bodyTableView.tableView.reloadData()
                }))
                alert.addAction(UIAlertAction(title: "취소", style: .default))
                vc.present(alert, animated: true) }
        }.store(in: &cancellables)
        return button
    }()

삭제 후 다시 request를 하여 값을 가져오고, 이때는 어차피 빈배열, 그걸 reloadData를 통해 다시 갱신.

May-08-2024 13-49-57

최근 본책 10개만 나오게 표출.

무한스크롤 기능 구현 전에 과제를 읽어보니 최근꺼 10개만 나오게 하라고 되어있다. 그부분을 수정해 보려한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func getDocumentfromCoreData () {
        
        do {
            try context.fetch(request).publisher
                .flatMap { data in
                    Publishers.Sequence(sequence: [data])}
                .collect()
                .map { data in
                    let sorted = data.sorted { first, second in
                        first.date > second.date
                    }
                    return sorted
                }
                .assign(to: \.recentDocument, on: self)
                .store(in: &cancellables)
        } catch {
            
        }
    }

아무래도 이부분에 대해서 배열의 값을 10개까지만 하게 하면 될듯하다.

현재 저기에 있는 로직은 sequence를 통해 하나씩 [data]값을 하나의 publisher로 만들고, 그걸 flatmap을 통해 하나의 publisher로 바꾼뒤, collect를 통해 하나의 배열의 형태로 전환이 되고, 그걸 다시 최신순으로 배열을 하게하는 구조이다.

즉 최신순으로 배열한 값에서 다시 10개만 가져오게 하는 과정을 거치면 문제가 없을 듯 하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
do {
            try context.fetch(request).publisher
                .flatMap { data in
                    Publishers.Sequence(sequence: [data])}
                .collect()
                .eraseToAnyPublisher()
                .map { data in
                    var sorted = data.sorted { first, second in
                        first.date > second.date
                    }
                    if sorted.count > 10 {
                        sorted = sorted.prefix(10)
                    }
                    return sorted
                }
                .assign(to: \.recentDocument, on: self)
                .store(in: &cancellables)
        } catch {
            
        }

이렇게 했더니

1
Cannot assign value of type 'Array<Publishers.Sequence<[Publishers.Sequence<[RecentModel], Never>.Output], Never>.Output>.SubSequence' (aka 'ArraySlice<RecentModel>') to type '[Publishers.Sequence<[Publishers.Sequence<[RecentModel], Never>.Output], Never>.Output]' (aka 'Array<RecentModel>')

아주 장황한 에러가 난다. 에러 포인트가 뭔지 확인해보자

우선 type mismatch는 확실하다.

Array<Publishers.Sequence<[Publishers.Sequence<[RecentModel], Never>.Output], Never>.Output>.SubSequence 이게 현재 타입

Publishers.Sequence<[Publishers.Sequence<[RecentModel], Never>.Output], Never>.Output]

prefix를 사용한 타입.

저 둘의 차이는 바로 앞에 Array가 있고 없고이다.

sorted.prefix(10) 에다가 Array(sorted.prefix(10))이라고 Array로 감싸주면 해결이 될 문제로 보인다.

May-08-2024 15-06-02

확인완료

이젠 10개만 나온다.

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