프로젝트를 진행하면서,

서버에서 내려주는 데이터를 비율에 맞게

UIView를 StackView에서 동적으로 그려야 하는 상황이 있었고, 이를 정리하고자 한다.

 

StackView의 "Fill Proportionally" 를 활용한 게시글이 거의 없어서 도움이 되었으면 좋겠다.

 

최종 결과물 - Proportional UIView in StackView

 


1. 서버에서 내려오는 데이터를 통해 뷰의 비율을 계산하는 로직에 대해 설명하고,

2. 비율 커스텀 UIView(Proportional UIView)를 StackView에 그리는 방법에 대해 설명하겠다.

 

1번은 코딩테스트 문제같은 곳에서 볼 수 있을 것 같기도 하고, 나와 비슷한 상황의 사람에게 도움이 될 것 같아 겸사 겸사 정리한다.

(오직 Proportional UIView만 궁금하신 분은 글 중하단으로 내려가길 바란다.)

 

관련 샘플 소스 프로젝트는 글 최하단에서 다운 받을 수 있습니다.

 


0. 큰 틀

테이블뷰의 cell에 있는 StackView에,

흔히 생각하는 엑셀 표(UIView)를 비율에 맞게 동적으로 그린다고 생각하면 될 것 같다.

 


1. 서버에서 제공되는 데이터

1. 열의 개수(=column)를 줌.

2. 데이터를 열의 개수에 맞게 줌. (단, 병합되어야 하는 열이면 데이터를 "merge" 라고 준다.)

 

예를 들어, 아래처럼 서버에서 데이터가 내려온다고 가정하자.

column = 4
data: [String] = ["40", "merge", "37", "99"]

그렇다면 StackView의 UIView들은 아래의 그림처럼 그려져야 할 것이다.

 

예시 설명 (2 : 1 : 1)


2. 비율 계산 로직

어떻게 그려져야 할지는 알겠다.

그렇다면 인지는... 어떻게 구현해야 할까?

 

현 상황을 정리해보자면,

초기 열(=column)의 개수를 전달받고 있고, 

"merge"를 만나면 앞의 인덱스와 합쳐져야 한다.

 

계산 로직

1. 각 열의 비율을 저장할 배열을 생성한다. proportionalValues = [1.0, 1.0, 1.0, 1.0]

2. for문을 거꾸로 접근한다.

3. 만약 데이터가 "merge"라면, 앞의 인덱스에 본인의 비율값을 넘겨주고 본인은 0.0으로 만든다.

 

이해를 돕기 위해 인덱스 순서대로 적어보자면,

[1.0, 1.0, 1.0, 1.0] → [1.0, 1.0, 1.0, 1.0] [2.0, 0.0, 1.0, 1.0] [2.0, 0.0, 1.0, 1.0]

따라서, [2.0, 0.0, 1.0, 1.0]이 되는 것이다.

 

(0값은 필터를 걸어주면) 최종 비율 2 : 1 : 1 이라는 결과를 얻을 수 있다!

 

var proportionalValues: [Double] = Array(repeating: 1.0, count: column) // 비율값 저장 배열.

for i in stride(from: column - 1, through: 0, by: -1) { // merge를 위해 역순으로 참조.
    if data[i] == "merge" {
        proportionalValues[i - 1] += proportionalValues[i] // 이전 열에 비중 합침.
        proportionalValues[i] = 0.0
    }
}

 

위 로직을 사용하면, 이런 case가 아니더라도 1:3:2 등 모든 case에 적용이 된다.

이해를 돕기 위해 한번 더 설명하면,

만약 내려오는 data = ["a", "q", "merge", "merge", "YY", "merge"] 라면,

[1.0, 1.0, 1.0, 1.0, 2.0, 0.0]  [1.0, 1.0, 1.0, 1.0, 2.0, 0.0]  [1.0, 1.0, 2.0, 0.0, 2.0, 0.0]  [1.0, 3.0, 0.0, 0.0, 2.0, 0.0]  [1.0, 3.0, 0.0, 0.0, 2.0, 0.0]  [1.0, 3.0, 0.0, 0.0, 2.0, 0.0]

(0값은 필터를 걸어주면) 최종 비율 1 : 3 : 2 이라는 원하는 결과를 얻을 수 있다!

 

(더 좋은 방법이 있다면 댓글로 공유해주시면 감사하겠습니다.)

 


3. Proportional UIView를 StackView에 그리는 방법

 

ProportionalUIView를 만드는 법은 간단하다.

 

아래의 커스텀 UIView를 생성 및 상속받고, (ProportionalUIView)

아래의 함수들을 호출해서, ( calculateProportional(), drawProportionalView() )

StackView 안에 들어가는 subView의 비율들만 지정해서 넣어주면 된다.

 

커스텀 UIView 생성(ProportionalUIView)

import UIKit

class ProportionalUIView: UIView {

    var width = 1.0
    
    override var intrinsicContentSize: CGSize {
        return CGSize(width: width, height: 1.0)
    }

}

 

비율 계산 함수 호출(calculateProportional)

func calculateProportional(_ column: Int, _ data: [String]) -> [Double] { // 비율 계산 함수
    var proportionalValues: [Double] = Array(repeating: 1.0, count: data.count)

    for i in stride(from: column - 1, through: 0, by: -1) { // merge를 위해 역순으로 참조.
        if data[i] == "merge" {
            proportionalValues[i - 1] += proportionalValues[i] // 이전 열에 비중 합침.
            proportionalValues[i] = 0.0
        }
    }
    return proportionalValues
}

 

stackview에 uiview 그리는 함수 호출(drawProportionalView)

 

func drawProportionalView(_ column: Int, _ proportionalValues: [Double]) {
    var proportionalSubViews: [ProportionalUIView] = [] // 비율대로 생성할 subViews 저장 배열.

    for i in 0..<column {
        let tempView: ProportionalUIView = ProportionalUIView()
        tempView.width = proportionalValues[i] // 비율만큼 비중 세팅.

        tempView.backgroundColor = UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0) // view 구분을 위해, 랜덤배경색 추가.

        proportionalSubViews.append(tempView) // 비율에 맞게 세팅된 tempView들 우선 저장.
    }

    let _ = proportionalSubViews.map { // 스택뷰에 tempView추가.
        stackView.addArrangedSubview($0)
    }
}

 

주의!

StackView의 설정 > Distribution > Fill Proportionally로 설정해야 한다. (우측 상단)

 

StackView 설정 - Fill Equally 설정.

 

그렇지 않으면 아래의 사진처럼 다 깨진다!! (오른쪽에 아주 좁게 다른 영역이 보이는가?)

 

StackView 설정 - Fill 설정 시

 


Proportional View 최종 결과물

StackView에서, (1 : 3 : 1) 의 원하는 비율대로 잘 나오는걸 확인할 수 있다.

 

Proportional View in StackView 최종 결과물 / 1 : 3 : 1

 

설명 겸 정리를 위해 만든 샘플 프로젝트 소스도 함께 첨부하며, 이번 글을 마친다.

 

ProportionalUIVIewSampleProject.zip
0.07MB

 


참고 자료1: https://spin.atomicobject.com/2017/02/07/uistackviev-proportional-custom-uiviews/

참고 자료2: https://stackoverflow.com/questions/46253248/uistackview-proportional-layout-with-only-intrinsiccontentsize

반응형