본문 바로가기
iOS

[UIKit] 그라데이션 라인 TableViewCell에 적용하기

by DuncanKim 2023. 10. 6.
728x90

[UIKit] 그라데이션 라인 TableViewCell에 적용하기

 

 

프로젝트를 진행중인데, 화면 좌측의 이어지는 그라데이션 라인을 구현해야 했다. 하나의 스크롤 뷰 안에 HStack을 두고, 긴 라인으로 구현을 할 수도 있을 것이다. 그러나, 추후 구현해야 할 사항 중, 그룹 추가뷰에서는 셀의 이동이 예상되었기도 했기에, TableViewCell 내부에 라인을 구현하는 것이 필요했다.

TableViewCell은 각 셀이 나누어져있다. 그렇기 때문에, 하나의 그래디언트 라인을 그리고 화면에 표시한다는 것은 불가능한 일이다. 뭐, 스크롤뷰를 두 개로 나누어 하나는 테이블뷰셀, 하나는 라인뷰를 보여준다면, 가능할 수도 있겠지만,, 너무 복잡하다는 생각이 들었다.

각각의 선들이 테이블뷰 셀에 포함되지만, 각 셀에 포함되는 선이 자연스럽게 한개의 이어지는 선인 것처럼 표현하는 것이 필요했다.

(작업자도 작업에 대한 정의를 무엇이라고 설명하는 것이 약간 복잡할 정도긴 하다.)

 

본 작업에는 세 가지 산이 있었다.

  • 어느 정도의 비율의 그래디언트 라인을 표현해야 하는 것인가?(수학적인 계산 문제)
  • data를 셀에 전달하는 부분(초기화 과정에서 변수 세팅 전, 뷰를 그려버리는 문제)
  • 셀 부분 별로 그래디언트 라인을 그리기 위한 방법(코딩 방법론적 문제)

 

1. 수학적 계산 문제

 

 

셀은 유동적으로 늘어나거나 줄어들게 되어 있다. 출발지와 도착지는 항상 있지만, 경유지는 1개일 수도, 2개 일수도 있다. 그렇기에, 셀 길이 자체가 늘어날 수 있다. 하나의 셀이 위치해있는 곳의 비율을 계산해주는, 다시 말해, 전체의 그래디언트 라인이 있다면, 0 ~ 1까지의 지점 중에 어떤 좌표값의 색상을 보여줘야 하는 지가 문제가 되는 것이다.


ex.

2개의 셀이 있다면,

1번 셀의 경우, 0 ~ 0.5 사이의 그래디언트 라인을 보여줘야 한다.

2번 셀의 경우, 0.5에서 1 사이의 그래디언트 라인을 보여줘야 한다.

3개의 셀이 있다면,

1번 셀의 경우, 0 ~ 0.33 사이의 그래디언트 라인을 보여줘야 한다.

2번 셀의 경우, 0.33 ~ 0.66 사이의 그래디언트 라인을 보여줘야 한다.

3번 셀의 경우, 0.66 ~ 1 사이의 그래디언트 라인을 보여줘야 한다.


전체 보여줘야 할 셀의 개수와 해당 셀의 index로 저 구간을 알 수 있을 듯 했다.

(1 / 전체 셀의개수) * (현재 셀의 index) ~ (1 / 전체 셀의 개수) * (현재 셀의 index + 1 → (그 다음 셀의 인덱스를 의미))

이 구간 사이의 색을 알면, 특정 셀에 맞는 색상을 찾아 넣어 줄 수 있을 것이라 생각했다.

식을 도출하고, 다음으로 넘어갔다.

 

 

2. 초기화와 변수 세팅, 그리고 뷰

 

전체 셀의 개수와 현재 셀의 index를 TableViewCell 클래스에서 알기 위해서는 TableViewDelegate 메서드에서 cell에 값을 넘겨주는 과정이 필요하다.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = GroupDetailTableViewCell(
            index: CGFloat(indexPath.row),
            cellCount: CGFloat((selectedGroup?.stopoverPoint.count ?? 0) + 2)
        )
}

cellForRowAt에서 indexPath.row를 넘겨주고, 그 인덱스에 맞게 색상 구간을 정의해줘야 하는데,

값이 자꾸 넘어가지 않는 상황이 발생했다.

뷰가 이미 그려지고 나서 index가 들어가서 구간 설정이 안된채로 선이 그려졌었다.

 

(지금 생각해보면, 다 그려지고 나서 reloadData() 하면 되지 않을까 한다.)

 

그 때 당시에 생각에는 미리 값을 세팅하고 나서 그려야 한다는 생각이 머리에 깊게 자리잡았다.

 

그래서 억지로

init(
        index: CGFloat,
        cellCount: CGFloat,
        style: UITableViewCell.CellStyle = .default,
        reuseIdentifier: String? = nil
    ) {
        self.index = index
        self.cellCount = cellCount
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupUI()
        setupConstraints()
    }

이렇게 GroupDetailTableViewCell에 초기화를 해주었다.

그렇게 하고 나니, 값은 받아와졌다.

 

// TODO: -

(추후 cellForRowAt 메서드에서 cell의 프로퍼티 값을 바꿔주는 방식으로 변경하고, 뷰를 업데이트 하는 방법으로 변경시켜볼 예정)

 

3. 그래디언트 라인을 그리기 위한 방법(문제 정의)

긴 라인을 가상으로 그리고 나서, 그 사이의 색상을 추출해야 하겠다는 첫 생각을 가지고 접근했다.

하지만, 너무 코드가 복잡해지며, 이것이 정답이 아니라는 생각이 들었다.

 

하나의 생각이 빤짝하고 떠올랐다.

1) hex 코드도 어쨌든 숫자이고, 그라데이션도 그 중간의 색상값들을 차례대로 색을 바꿔가면서 표현하는 것이다.
2) rgb 값으로 다시 가져와서 특정 구간의 시작점 색상과 종료점 색상을 추출하고나서,
3) 각각의 셀의 스타트 포인트와 엔드 포인트 사이의 선을 그려주면 되는 것 아닌가…

 

라는 생각이 들었다.

override func draw(_ rect: CGRect) {
        // 그래디언트 레이어 생성
        let gradientLayer = CAGradientLayer()
        let color = calculateColor(indexPath: index, cellCount: cellCount)

        gradientLayer.frame = rect
        gradientLayer.colors = [UIColor(hex: color[0])?.cgColor as Any, UIColor(hex: color[1])?.cgColor as Any]
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)

        // 실선을 그릴 베지어 패스 생성
        let path = UIBezierPath()
        path.move(to: CGPoint(x: rect.midX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))

        // 실선 스타일 설정
        path.lineWidth = 2.0
        let dashes: [CGFloat] = [4, 4]
        path.setLineDash(dashes, count: dashes.count, phase: 0)

        // 그래디언트 레이어를 뷰에 추가
        self.layer.addSublayer(gradientLayer)
    }

 

그래디언트 레이어를 그리는 코드이다.

각 셀 별로 왼쪽 선이 y: 0부터 y: 1까지 그려지게 되는데, 그 시작점의 색상과 종료점의 색상을 각각 다르게 해주는 것이다.

그 색상을 구하는 메서드는 아래와 같다.

private func calculateColor(indexPath: CGFloat, cellCount: CGFloat) -> [String] {
        // 상수 색 RGB 값으로 변경(RGBA 순)
        let startColor: [CGFloat] = [44, 255, 220]
        let endColor: [CGFloat] = [98, 122, 243]

        // 시작 및 끝 색상의 RGB 값을 계산
        var startColorRGB: [CGFloat] = []
        var endColorRGB: [CGFloat] = []

        for index in 0..<3 {
            let distance = endColor[index] - startColor[index]
            let startColorStd = (distance / cellCount * indexPath) + startColor[index]
            let endColorStd = (distance / cellCount * (indexPath + 1)) + startColor[index]
            startColorRGB.append(startColorStd)
            endColorRGB.append(endColorStd)
        }

        // 계산된 RGB 값을 16진수 Hex 코드로 변환
        let startColorHex = UIColor.rgbToHex(
            red: round(startColorRGB[0]) / 255,
            green: round(startColorRGB[1]) / 255,
            blue: round(startColorRGB[2]) / 255
        )
        let endColorHex = UIColor.rgbToHex(
            red: round(endColorRGB[0]) / 255,
            green: round(endColorRGB[1]) / 255,
            blue: round(endColorRGB[2]) / 255
        )

        return [startColorHex, endColorHex]
    }

우리는 사용하는 그라데이션 색상이 같았다. 그래서 RGB 값을 상수로 선언해주었다.

거기에서, 각각의 인덱스와 전체 셀의 개수에 대비해서, 스타트 포인트의 색상과, 엔드 포인트의 색상을 리스트로 반환해주는 코드를 작성했다.

 

문제정의가 가장 중요했으며, 문제정의를 잘 하고 나니, 금방 코드를 구현할 수 있었다.

728x90

댓글