TIL(230212)

Youth·2023년 2월 12일
0

1. DiffableDataSource 사용해보기

아카데미에 있을때부터 tableView나 collectionView관련해서 데이터를 추가하거나 필터링을 할때 매번보던게 DiffableDataSource였는데 이번기회에 한번 사용해봐야겠다 싶어서 한번 사용해봤다

그전에 간단하게 설명을 하자면 기존에 우리가 tableView를 사용하면 데이터를 추가하거나 필터링하거나 삭제하면 꼭 써주던 메서드가 있었다

tableView.reloadData()

이렇게하면 모든 데이터를 다시불러와서 변경사항이있는 데이터대로 tableView를 만들어준다
사실 이렇게 하는거에 문제가 있는건 아니다 그리고 이건 그냥 블로그들을 찾으면서 공통적으로 나오는말인데 WWDC에서 diffableDataSource를 설명할때도 reloadData는 좋은방법이라고 했다 근데 문제는 유저의 경험에 약간 안좋을수있다는건데 reloadData와 diffableDataSource의 가장큰 차이점은 tableView의 변경사항을 애니메이션을 넣어 조금더 자연스럽게(슥슥 추가된다던지 슥슥 없어진다던지) 보여준다는것이다

요약하자면
1. reloadData()가 결코 나쁜방법이 아니다
2. diffableDataSource는 UX적으로 조금은 나은 방식이다

자 우선 기본적으로 테이블뷰를 정의해준다 늘하던대로

private let tableView = UITableView()

그리고 나서 우리는 diffableDataSource와 snapShot이라는걸 선언해줘야한다(중요)

var diffableDataSource: UITableViewDiffableDataSource<Section, Person>!
var snapShot: NSDiffableDataSourceSnapshot<Section, Person>!

제네릭으로 Section과 Person이 있는데 앞에건 말그대로 section이고 뒤에건 어떤 데이터가 들어갈지라고 생각하면 된다

중요한건 section이나 들어갈 데이터(여기서는 person구조체)는 hashable해야한다는것이다
내가 만든 section과 person을 보면

struct Person: Hashable {
    let name: String
    let age: Int
    let address: String
}

enum Section {
    case main
}

이렇게 되어있는데 기본적으로 section은 enum으로 주로한다 왜냐면 enum자체가 연관값이 없으면 hashable하기 때문이다. 그리고 구조체 선언할떄 Hashable프로토콜을 채택해주면 된다.
클래스로 hashable하게 선언하는 방법은 구글링하면 나온다 이번에는 구조체로 해볼예정이다.

그러고 나서 늘 하던대로 선언한 tableView에 register 메서드를 사용해주고

중요한 diffableDataSource를 정의하면된다. 여기서 주의할점은 diffableDataSource자체가 기존에 UITableViewDataSource라는 프토토콜을 "대체"하기 때문에 ViewContorller가 UITableViewDataSource을 채택할필요가 없다.

보통 여기서 tableViewCell의 갯수와 Cell을 연결해주는데 이걸 diffableDataSource에서 해주면된다

        diffableDataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in
            let cell = DiffableTableViewCell.reusableCell(tableView: tableView)
            // MARK: - indexPath.row를 사용하는게 아니라 그냥 itemIdentifier를 넣어주면 된다
            cell.data = itemIdentifier
            return cell
        })

위와같이 해주면되는데 중요한부분은 itemIdentifier이다. 나도 처음에 이게뭔가했었는데 구글링해보니 기존에 indexPath.row를 대체할수있다고한다
원래같으면 아마 cell.data = people[indexPath.row]이런식으로 people이라는 [Person]타입에서 하나씩 꺼내서 cell에다가 데이터를 전달해줬을텐데 그냥 제네릭으로 Person을 지정해둬서 바로바로 데이터를 전달해준다... 이건 매우 편리한 기능인거같다

그리고 위에서 정의한 snapShot에 snapShot인스턴스를 할당해준다

snapShot = NSDiffableDataSourceSnapshot()

그리고 다음부턴 순서만 잘기억하면된다

// 1. snapShot에 Section추가(enum으로 만들어놓는게좋음 hashable하기때문)
snapShot.appendSections([.main])

// 2. snapShot에 Items를 추가
snapShot.appendItems(viewModel.people, toSection: .main)

// 3. 4번에서만든 diffableDataSource에 snapShot을 apply
diffableDataSource.apply(snapShot, animatingDifferences: true)

아까전에 snapShot이라는 개념을 언급했었는데 정확하진 않지만 내가 이해한바로는 snapShot이라는 캡쳐본을 찍어놓고 다른 snapShot이 apply되면 기존 snapShot과 비교해서 다른부분을 업데이트 해주는 그런방식인거같다 그래서 예를 들어서 나는 기존에 tableView에다가 delegate로 데이터를 전달해주고 tableView에 한줄을 추가해주는 코드를 짰는데 아래와 같이 새로운 snapShot을 적용시켜주면된다

extension ViewController: CustomDiffableDataSourceDelegate {
    func addPerson(person: Person) {
        viewModel.addPerson(person: person)   
        // 뭔가가 바뀔때마다 snapShot을 만들어주고 item을 넣은다음에 dataSource에 apply해주면 기존 snapShot과 비교해서 ui를 변경해주는 느낌
        var snapShot = diffableDataSource.snapshot()
        snapShot.appendItems([person], toSection: .main)
        diffableDataSource.apply(snapShot, animatingDifferences: true)
    }
    func deletePerson(index: Int) {
        viewModel.deletePerson(index: index)
    } 
}

아그리고 추가로 데이터 삭제는 tableView자체에 드래그해서 삭제하는 기능을 사용헀는데 이걸 사용하려면 DiffableDataSource를 Custom해야한다고 해서(기존 uitableViewDataSource에있는 메서드 사용해야함) 아래의 코드를 사용했다

class CustomDiffableDataSource: UITableViewDiffableDataSource<Section, Person> {
    
    var delegate: CustomDiffableDataSourceDelegate?
    
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        guard let item = itemIdentifier(for: indexPath) else {
            return
        }
        var snapShot = self.snapshot()
        snapShot.deleteItems([item])
        delegate?.deletePerson(index: indexPath.row)
        self.apply(snapShot, animatingDifferences: true)
    }
}

2. UIButton Configuration 사용해보기

WWDC에서 버튼을 만들수있는 새로운 방식을 소개했었다(ios 15부턴가 사용이가능한걸로알고있다)
그래서 어떤프로젝트를 할때마다 버튼은 만드는데 한번 써보자생각해서 말그대로 써보기만 했다

기존에 버튼을 만들거나 이걸 컴포넌트화 하려면 class를 만들어사용해왔다. 사실딱히 불편하지는 않았는데 신기술이라고 하니 한번 써봤다

    var configuration: UIButton.Configuration = {
        var config = UIButton.Configuration.tinted()
        config.title = "test3button"
        config.baseBackgroundColor = .red
        config.baseForegroundColor = .blue
        config.cornerStyle = .capsule
        return config
    }()

우선 configuration이라는게 버튼을 어떤모양으로만들거니~ 이런걸 미리 정의 해놓고

lazy var test3Button = UIButton(configuration: configuration)

이렇게 uibutton에 input으로 정의해놓은 configuration에 넣어주면 된다
사실 크게 다를게 없다

그래서 여러가지를 만들어봤다

    var configuration: UIButton.Configuration = {
        var config = UIButton.Configuration.tinted()
        config.title = "test3button"
        config.baseBackgroundColor = .red
        config.baseForegroundColor = .blue
        config.cornerStyle = .capsule
        return config
    }()
    lazy var test1Button = UIButton(configuration: .tinted())
    lazy var test2Button = UIButton(configuration: .customStyle(style: .a, title: "test2button"))
    lazy var test3Button = UIButton(configuration: configuration)
    
    lazy var test4Button: UIButton = {
        let button = UIButton(configuration: .customStyle(style: .b, title: "test4button"))
        button.addTarget(self, action: #selector(test4ButtonTapped), for: .touchUpInside)
        return button
    }()

1번버튼같은 경우는 기본 애플에서 제공해주는 configuration인 .tint()를 사용했고
2번버튼은 configuration자체를 extenstion으로만들어서 사용해봤다
3번버튼은 configuration을 선언해서 넣은 예제이고
4번버튼은 기존에 uibutton을 만드는 방식에 configuration을 사용해봤다

2번버튼에 넣은 custom configuration은 아래와같이 만들었다

extension UIButton.Configuration {
    
    enum CustomButtonStyle {
        case a, b, c
        
        var backgroundColor: UIColor? {
            switch self {
            case .a:
                return .blue
            case .b:
                return .black
            case .c:
                return .red
            }
        }
        
        var foregroundColor: UIColor? {
            switch self {
            case .a:
                return .brown
            case .b:
                return .green
            case .c:
                return .white
            }
        }
    }
    
    static func customStyle(style: CustomButtonStyle, title: String) -> UIButton.Configuration {
        var configuration = UIButton.Configuration.filled()
        configuration.title = title
        configuration.titleAlignment = .center
        configuration.baseForegroundColor = style.foregroundColor
        configuration.baseBackgroundColor = style.backgroundColor
        configuration.background.cornerRadius = 20
        return configuration
    }
}

ios15부터라서 사실 당장쓰이기는 어렵겠지만 디자인시스템을 만들때 ios버전 15이상이 타겟이라면 고려해보면 좋을거같다는 생각을 해봤다

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글