Ch.9 Gold: Favorite Items

sun·2022년 2월 4일
0

# 최애템 표시할 수 있게 하기

  • 어떤 아이템을 오른쪽으로 스와이프하면 최애템이라는 표시가 뜨게 하기

  • 보너스로는 버튼 등을 통해 최애템만 나타내게 하기


# tableView(_:leadingSwipeActionsConfigurationForRowAt:)

  • 공식문서를 보니까 이 친구가 leading 에서 시작하는 스와이프를 인식해서 그에 따른 액션을 하는 메서드였다. 처음에는 그냥 스와이프 된 아이템의 isFavorite 프로퍼티를 true 로 바꿔주고 reloadRows(at:with) 메서드로 다시 로드 때렸다...

  • 이렇게 하면 스와이프 시 최애템 설정이 되기는 하는데 정말 살짝만 스와이프를 해도 최애템으로 설정이 되어버렸다...왼쪽 스와이프 시 삭제되는 것과 비슷하게 하고 싶어서 메서드를 살펴봤더니 리턴값인 UISwipeActionsConfiguration 로 스와이프 시 액션을 지정할 수 있었다.

  • UISwipeActionsConfiguration 는 실제로 수행할 액션인 UIContextualAction 을 배열로 갖고 있으므로 UIContextualAction 를 내가 원하는 방식으로 설정해주면 될 것 같았다. 설정 방법은 솔직히 공식 문서만 보고서는 바로 이해가 안 가서 그냥 혼자 이것저것 뚝딱뚝딱 해보면서 깨달았다...

  • UIContextualAction(style:title:handler: UIContextualAction.Handler) 를 이용해서 생성했고, title 이랑 style 의 경우 초기화 시 바로 설정해줬고, 배경 색깔을 초기화 후 따로 background 프로퍼티를 통해 설정했다.

    • 아래 사진에서 초록색이 background 를, 글자가 title 프로퍼티를 통해 설정한 것
    • handler 의 경우 실제 스와이프가 발생했을 때 어떤 작업을 할 지인데 여기서 현재 스와이프한 아이템의 isFavorite 프로퍼티를 토글하고 리로드 했다. 이 작업에서 에러가 날 코드가 없어서 핸들러의 completion handler 는 그냥 true 을 넣어줬다. 뭐 아이템을 불러와서 써야되는 데 여기서 nil 이 반환될 수 있거나 했다면 조건문으로 true/false 경우를 나눠줬을 수 있을 거 같다!

class ItemsViewController: UITableViewController {
    ... 
    override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let markAsFavorite = UIContextualAction(style: .normal, title: "Favorite?") { _, _, completionHandler in
            
            let item = self.showOnlyFavaorites ? self.itemStore.favoriteItems[indexPath.row] : self.itemStore.allItems[indexPath.row]
            item.isFavorite.toggle()
            
            if self.showOnlyFavaorites {
                tableView.reloadData()
            } else {
                tableView.reloadRows(at: [indexPath], with: .automatic)
            }
            
            completionHandler(true)
        }

        markAsFavorite.backgroundColor = .systemGreen
        
        return UISwipeActionsConfiguration(actions: [markAsFavorite])
    }
}

# 최애템만 보기

  • 스위치를 추가해서 스위치를 토글하는 경우 최애템만 보여주도록 했다. ItemStore 에 favoriteItems 프로퍼티를 (관리가 쉬우라고) 연산 프로퍼티로 추가해서 썼고, ItemsViewController 에 showOnlyFavorites 프로퍼티를 추가해서 스위치의 토글 상태를 나타냈다.
    • 최애템만 보여줘야 하는 경우 델리게이트의 다양한 메서드에서 ItemStore 의 allItems 대신 favoriteItems 를 사용하도록 했다.
class ItemStore {
    var favoriteItems: [Item] {
        allItems.filter { $0.isFavorite }
    }
}

class ItemsViewController: UITableViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        showOnlyFavaorites ? itemStore.favoriteItems.count : itemStore.allItems.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
        
        let item = showOnlyFavaorites ? itemStore.favoriteItems[indexPath.row] : itemStore.allItems[indexPath.row]
        
        cell.textLabel?.text = item.isFavorite ? "(favorite) \(item.name)": item.name
        cell.detailTextLabel?.text = "$\(item.valueInDollars)"

        return cell
    }
    
    private var showOnlyFavaorites = false
    
    @IBAction func showOnlyFavorites(_ sender: UISwitch) {
        showOnlyFavaorites.toggle()
        tableView.reloadData()
    }
}

  • 이때 최애템만 보고 있는 상황에서 아이템을 추가, 삭제, 이동하는 경우 기록은 allItems 에 해야하는 데 indexPath 는 최애템 리스트를 기준으로 들어오기 때문에 문제가 됐다. 따라서 indexPath 를 각 메서드 적절히 allItems 기준으로 바꿔주는 등의 작업을 추가했다.
    • 예를 들어 최애템 리스트를 보고 있는 데 아이템을 추가하는 경우에는 일단 추가되는 아이템은 일반 아이템인 게 디폴트이므로 현재 테이블에는 추가할 필요가 없기 때문에 insertRows(at:with) 메서드를 호출하지 않도록 했다.
    • 최애템만 보고 있는데 아이템 순서를 바꾸는 경우는 allItems 를 기준으로 순서를 바꿔줘야 해서 인덱스 변환도 하고 ItemStore 에서 swapItemsAt(_:_:) 메서드를 추가했다.
class ItemsViewController: UITableViewController {
    
    var itemStore: ItemStore!
    
    @IBAction func addNewItem(_ sender: UIButton) {
        let newItem = itemStore.createItem()
        
        if let index = itemStore.allItems.firstIndex(of: newItem) {
            let indexPath = IndexPath(row: index, section: 0)
            // 모든 아이템을 보고 있을 때만 테이블에 추가
            if !showOnlyFavaorites {
                tableView.insertRows(at: [indexPath], with: .automatic)
            }
        }
    }
    
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            let item = showOnlyFavaorites ? itemStore.favoriteItems[indexPath.row] : itemStore.allItems[indexPath.row]
            
            itemStore.removeItem(item)
            
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }
    }
    
    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        if showOnlyFavaorites {
            let fromItem = itemStore.favoriteItems[destinationIndexPath.row]
            let toItem = itemStore.favoriteItems[sourceIndexPath.row]
            
            let fromIndex = itemStore.allItems.firstIndex(of: fromItem)!
            let toIndex = itemStore.allItems.firstIndex(of: toItem)!
            
            itemStore.swapItemsAt(fromIndex, toIndex)
        } else {
            itemStore.moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
        }
    }
}

class ItemStore {
    func swapItemsAt(_ fromIndex: Int, _ toIndex: Int) {
        if fromIndex == toIndex {
            return
        }
        
        allItems.swapAt(fromIndex, toIndex)
    }
}

  • 문제는 마지막에 기왕이면 오른쪽 스와이프마다 최애템을 설정/해제할 수 있도록 하고 싶어서 toggle 로 바꿨더니 최애템 리스트를 보고 있는 상태에서 아이템을 삭제하면 현재 스와이프한 행이 (더 이상 최애템이 아니라) 사라지므로 reload 할 행이 사라져서 앱이 터졌다. 그래서 최애템 리스트를 보고 있는데 스와이프가 들어오는 경우 reloadData() 메서드를 호출해서 테이블 전체를 다시 로드하도록 했다.
class ItemsViewController: UITableViewController {
    override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let markAsFavorite = UIContextualAction(style: .normal, title: "Favorite?") { _, _, completionHandler in
            
            let item = self.showOnlyFavaorites ? self.itemStore.favoriteItems[indexPath.row] : self.itemStore.allItems[indexPath.row]
            item.isFavorite.toggle()
            
            if self.showOnlyFavaorites { 
                tableView.reloadData()  // 바로 여기
            } else {
                tableView.reloadRows(at: [indexPath], with: .automatic)
            }
            
            completionHandler(true)
        }

        markAsFavorite.backgroundColor = .systemGreen
        
        return UISwipeActionsConfiguration(actions: [markAsFavorite])
    }
}
profile
☀️

0개의 댓글