프로젝트를 진행하면서,
서버에서 내려주는 데이터를 비율에 맞게
UIView를 StackView에서 동적으로 그려야 하는 상황이 있었고, 이를 정리하고자 한다.
StackView의 "Fill Proportionally" 를 활용한 게시글이 거의 없어서 도움이 되었으면 좋겠다.
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. 비율 계산 로직
어떻게 그려져야 할지는 알겠다.
그렇다면 몇대 몇대 몇인지는... 어떻게 구현해야 할까?
현 상황을 정리해보자면,
초기 열(=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로 설정해야 한다. (우측 상단)
그렇지 않으면 아래의 사진처럼 다 깨진다!! (오른쪽에 아주 좁게 다른 영역이 보이는가?)
Proportional View 최종 결과물
StackView에서, (1 : 3 : 1) 의 원하는 비율대로 잘 나오는걸 확인할 수 있다.
설명 겸 정리를 위해 만든 샘플 프로젝트 소스도 함께 첨부하며, 이번 글을 마친다.
참고 자료1: https://spin.atomicobject.com/2017/02/07/uistackviev-proportional-custom-uiviews/
최근댓글