ondelva

← Archive

iOS 카메라 개발기 - 기본 카메라 UI를 픽셀로 재다

애플 기본 카메라의 줌 다이얼을 따라 그리려고 스크린샷을 극좌표로 펼치고 밝기 단면을 쟀다. 눈으로 '잘렸다'고 본 것은 그라데이션이었다.

egg2분 읽기

카메라 앱을 만들면 기본 카메라와 비교를 피할 수 없다. 사용자 손은 이미 기본 카메라에 익어 있어서 줌 다이얼이 조금만 달라도 어색하다.

그래서 지난 편의 줌 다이얼을 기본 카메라에 맞추기로 했다. 애플은 치수를 공개하지 않으니 스크린샷을 재는 수밖에 없었다. 기기는 iPhone 17 Pro, iOS 26.6.1.

다이얼을 극좌표로 펼쳤다

라벨의 x좌표를 읽어서 눈금 간격을 역산하는 방법도 있는데 원 위에 놓인 걸 직교 좌표로 읽는 셈이라 정밀하지 않다.

그래서 펼쳤다. 스크린샷에서 다이얼의 원을 피팅하고 각도 × 반지름으로 펼쳐 띠 모양의 이미지를 만든다. 그 위에서 밝기 단면을 재면 눈금이 직선 위에 늘어선다.

알아낸 것은 이렇다.

  • 눈금 축은 옥타브당(배율 두 배마다) 48pt로 균일하다
  • 반지름은 240pt
  • 두꺼운 눈금은 정수 배율 자리
  • 포인터 주변이 파인 모양은 V자가 아니라 지수 곡선이다. 호를 따라 3.3pt마다 1/e로 줄어든다
  • 다이얼 양 끝은 잘린 게 아니라 그라데이션이다

마지막 것은 처음에 틀렸다. 눈으로 보고 “끝이 잘려 있다”고 판단해서 그렇게 그렸는데 단면을 재보니 서서히 사라지고 있었다. 눈으로는 경계만 보이고 밝기가 줄어드는 건 안 보였던 것 같다.

캡슐 높이는 세로 가운데에서 잰다

줌 버튼 캡슐의 높이를 쟀더니 34pt가 나왔다. 그걸로 그렸다가 되돌리고 다시 재서 또 되돌렸다.

모서리 근처에서 재고 있었다. 둥근 모서리 쪽은 높이가 줄어드니까 당연히 작게 나온다. 세로 중앙선에서 재니 48pt였다. 두 번 되돌린 끝에야 알았다.

폰트 크기는 글리프 폭으로 역산한다

라벨 폰트도 공개된 값이 없다. SF Rounded인 건 모양으로 알 수 있는데 크기와 굵기는 재야 했다.

후보 크기와 굵기마다 같은 문자열의 폭을 계산해서 스크린샷의 잉크 폭과 맞췄다.

for size in stride(from: 14.0, through: 18.0, by: 0.5) {
    let base = UIFont.systemFont(ofSize: size, weight: .semibold)
    let font = UIFont(descriptor: base.fontDescriptor.withDesign(.rounded)!, size: size)
    let w = (label as NSString).size(withAttributes: [.font: font]).width
    print(size, w)
}

SF Rounded Semibold 16pt에서 소수점까지 맞았다. 높이로도 해봤는데 잉크 높이는 1pt 넘게 어긋나서 판별에 못 썼다.

progress >= 1은 오지 않았다

지수 곡선을 따라가는 값에 progress >= 1이 되면 끝내는 분기를 뒀더니 영원히 안 걸렸다. 지수 곡선은 1에 닿지 않는다.

let progress = 1 - exp(-t / tau)
if progress > 0.999 { finish() }   // >= 1은 영원히 안 온다

다이얼을 잘라내는 순서에서도 한 번 걸렸다. .clipped()를 .scaleEffect보다 먼저 두면 잘린 단면이 같이 축소돼서 끝이 드러난다.

// bad case - 잘린 가장자리가 축소를 따라온다
dial.clipped().scaleEffect(s)

// good case - 넓게 그리고, 줄인 다음에 자른다
dial.scaleEffect(s).clipped()

결국 나란히 놓고 맞췄다

세이프 에어리어 기준 오프셋 같은 위치값은 계산으로 맞추려다 여러 번 어긋났다. 기본 카메라 스크린샷과 내 스크린샷을 나란히 놓고 조정하는 게 제일 빨랐다.

돌아보면 재서 얻은 값은 믿을 만했는데 계산으로 낸 값은 번번이 틀렸다.

겹쳐 그리는 두 요소는 좌표를 따로 계산하지 말고 같은 ZStack에 넣어 원점을 공유시킨다. 이때 .overlay는 부모 레이아웃 크기에 영향을 주지 않고 opacity(0)은 크기를 그대로 차지한다는 걸 알아두면 덜 헤맨다.

다이얼 하나 따라 그리는 데 이 정도로 잴 줄은 몰랐다. 애플이 치수를 공개해줬으면 이 편은 없었을 거다.

다음 편은 화면 밖 이야기다. 잠금 화면과 제어센터에서 카메라를 켜는 법.

Filed under 개발

ShareXFacebookLinkedIn