How to use FlexLayout effectively & Sunsetting Texture(AsyncDisplayKit)

David Ha (Hyeonsu)
당근 테크 블로그
10 min readDec 11, 2021

SwiftUI/Jetpack compose/Flutter 와 같이 선언형으로 UI를 개발하고 Flex에 대한 개념이 어느정도 네이티브 생태계에 퍼진거 같아 매우 기쁘지 않을 수가 없다.

초창기 Frame계산을 해서 Layout을 배치하기 시작해서 constraints와 layout margin을 활용하거나 Storyboard/Xib상에서 UI를 배치하는 것에 넘어서 말이다.

4년 전부터 지금까지 Texture를 주로 활용해왔으나, 최근 수 많은 문제점들을 겪어 온 일들이 있었는지 이야기 해볼 것이며 또한 현업 프로젝트를 온전히 SwiftUI 100%활용할 수 없는 과도기 기간에서 어떻게 지혜롭게 풀어나갈 것인지에 대해서 이야기 해볼려고 한다.

https://texturegroup.org/

Sunsetting Texture

Texture는 결코 나쁜 UI Framework는 아니다.

이 프레임워크는 우리에게 많은 영감을 주었고, UIKit을 잘 다루기 위한 수 많은 노하우를 전수해준 역사서적과 같다.

하지만, 디바이스 성능의 향상과 더불어 SwiftUI의 등장 그리고 커뮤니티와 메인테이너들의 소통 부재등과 같은 수 많은 외부적인 요인들이 Texture를 사용하는 개발자에게 좋지 않은 영향을 주게 되었다.

좀 더 구체적으로 나열해보자면

  • Adlai holler, appleguy, huy, moon과 같은 주요 메인테이너 분들이 구글등과 같은 회사로 이직과 같은 요인으로 대부분 동시에 자취를 감추었음.
  • 프레임 워크 내부 기술적인 부채 및 복잡성이 여전히 해소되고 있지 않고, iOS SDK가 새로 출시 될 때 마다 나오는 버그들을 동작이 될 정도로 구멍을 매꾸는 정도로 끝남.
  • 여전히 핀터레스트와 밀접된 많은 의존성과 팀 내에서 해당 의존성들을 유지보수 해줄 용사?들 부재.
  • 여러 동료들이 Texture를 경험했지만, 여전히 높은 러닝커브와 알 수 없는 UI버그 발생시 다가오는 큰 스트레스. 특히, objective-c를 이해하기 힘든 동료에겐 더 크나큰 스트레스로 작용함.
  • 메모리 사용에 부담이 큰 기능들엔 부적합함. 커뮤니티에서 이와 같은 문제점에 대해서 해결을 요구했지만, 여전히 이를 해소해주질 못하고 있음.

그래서 Texture를 사용했던 것이 당근마켓 iOS 동료들에게 결국 부정적인 결과를 초래했는가?를 이야기 해보면 그렇지는 않다.

Texture를 통해서 당근마켓 iOS 개발자들이 얻은 경험은 다음과 같다.

  • 선언형으로 UI를 개발하는 감각을 키웠음.
  • Flex개념을 기반으로한 UI프레임워크를 자유롭게 다룰 수 있음. 또한 이해하는데 큰 부담이 되지 않음.
  • 어떻게 하면 Flex개념을 기반으로한 UI프레임워크를 가지고 생산성 향상을 높이는 방향으로 UI를 설계할 수 있는지 잘 알고 있음.
  • Texture내부를 하도 많이 까봐서 UIKit에 대한 깊은 이해도를 가지고 있음.

어차피 답정너?인 상황이긴 했지만, Ray(오강훈님)가 FlexLayout을 수면 위로 끌어올렸고, 필자 또한 매우 기쁘지 않을 수 가 없었다.

FlexLayout을 선호할 수 밖에 없었던 이유는

  • 개발경험이 크게 많이 다르지는 않음. Texture에 비해서 UIKit의 활용에 좀 더 집중할 수 있고 큰 제약이 없음.
  • YogaKit기반으로 만들어진 엄청 가벼운 framework고 Yoga 동작 매커니즘만 어느정도 이해하면 충분히 다양한 UI를 구성할 수 있음.
  • Flex개념을 제외하면 공부할게 정말 없을 정도로 러닝커브 따윈 없음.
  • constraints를 쓸 수 밖에 없는 상황에 있어서는 자매품 PinLayout 도 있어서 좋음.
  • Texture의 automatically manager subnodes 처럼 UIView addSubiew, removeFromSuperview 를 처리할 필요없이 definition만 잘하면 알아서 잘 처리됌.
https://github.com/layoutBox/FlexLayout

대부분은 https://github.com/layoutBox/FlexLayout README에 잘 가이드가 되어있어서 가볍게 훝어보기만해도 충분히 UI를 잘 구성할 수 있다.

그래서 소소한 미립자같은 TIP들을 좀 나열해볼려고한다.

소소한 FlexLayout 미립자 사용 TIP

Texture처럼 선언된 Layout 설계에 따라서 높이를 계산할 수 있음

UITableView기준으로 기본적으로 UITableViewAutomaticDimension을 반환하는데, FlexLayout을 이용하여 Cell의 Size를 계산하는데 있어서 아래와 같이 코드를 작성하여 간단히 처리할 수 있다.

위와 같이 flex의 layout method를 mode가 adjustHeight인 상태로 호출하게 되면,

https://github.com/layoutBox/FlexLayout/blob/a1669156492653e0946a14110ec01ad8669203cc/Sources/YogaKit/YGLayout.mm#L296

제약사이즈의 높이를 undefined로 주고 아래의 method(calculateLayoutWithSize) 를 호출하여 계산을 하게 된다.

그 이후로는 YogaKit에서 내부적으로 잘 계산을 해서 node(YGNodeRef)에 계산된 frame값을 저장한 후 이를 기반으로 View hierarchy에 반영해서 사용자에게 보여지게 된다.

Texture의 ASDisplayNode와 Yoga의 YGNodeRef 둘을 놓고 보면 공통적인 목표는 동일하다 View를 그리기 위한 단순 메타데이터다. FlexLayout의 최대 장점은 Texture처럼 개발자가 Node를 직접 만질 필요 없이 wrapping해준 flex개념들에 집중하면 된다는 점.

View의 Data에 따라 동적으로 View의 상태값을 바꿈과 동시에 레이아웃을 변경해야한다면?

Texture같은 경우에는 layoutSpecThatFits에 선언된 로직들을 특정 분기에 따라서 반환값만 달리하고 setNeedsLayout을 호출하면 손쉽게 레이아웃을 변경할 수 있다.

하지만 FlexLayout의 경우에는 define된 로직들을 동적으로 반환하는 형태의 메커니즘이 아닌게 pain-point다.

예를 들어 위의 코드에서 ownerImageView의 크기를 cell클릭에 따라서 크기를 가변한다고 상상해보자

상상예시

그럼 아래 코드의 처럼 작성 후 ownerImageView에 대해서 markDirty해주고 setNeedsLayout 을 호출하면 동작하겠지 싶지만…

아무일도 일어나지 않는다.

그렇다면 사이즈를 가변하기 위해선 어떻게 처리해야하는가?

정답은 flex값을 아래의 코드와 같이 가변 해준 다음에 setNeedsLayout을 호출해야한다.

size뿐만 아니라 shirnk/grow, direction, alignItems 등등 동일하다.

에니메이션은 어떻게 처리하나요?

FlexLayout animation

Animation도 큰 문제 없이 FlexLayout에서 자유롭게 다룰 수 있다.

이전과 동일하게 flex값을 가변하고 변경되는 대상에 markDirty 처리후 setNeedsLayout을 호출하고 난 다음에 아래와 같이 UIView.animate를 동작시키면 된다.

즉, setNeedsLayout을 호출해서 수동으로 layoutSubviews를 예약시키면

YogaKit에서 markDirty된 element에 대해서 layout을 새로 계산하게 되고, UIView.animate에서 animations 클로져내에서 layoutIfNeededs를 duration동안 동기적으로 작동시킴으로서 위와 같이 에니메이션이 동작하게 된다.

Layout에 추가 및 제거를 하고 싶어요

위와 같이 하나는 “an Active Record plugin that ~~”에 해당하는 description이 있는 UI가 있고 아래는 해당하는 description이 없다.

단순 isHidden 처리만으로는 layout이 정상적으로 반영되지 않는다.

따라서, YogaKit에선 isIncludedInLayout를 제공해주면 이 역시 FlexLayout에선 isIncludedInLayout 를 wrapping해서 제공해준다.

어차피 반드시 markDirty를 선언해줘야하니, 필자는 개인적으로 2번 안을 선호한다.

그리고 이거 또한 꿀팁이지만, 만약 descLabel뿐만 아니라 위 상단 title간의 간격까지 조절해야하는 상황도 있을 수 있다.

12.0와 descLabel을 같이 숨겨주고 싶을 때…

이럴 때 아래와 같이 view를 하나 생성하는 방법도 있지만

spacing과 descLabel과 한 묶음으로 사라져야 하는 상황이라면 차라리 아래와 같이 한묶음으로 containerView를 포지셔닝해서 containerView에 대해서 isIncludedInLayout 을 처리해주면 제어로직이 단순해진다.

마무리

다양한 방법으로 UI를 그릴 수 있다는건 입문한지 얼마안된 개발자에겐 고통일 수 있겠지만, 어쩌면 정답이 없는 세계에선 축복일 수도 있다. SnapKit으로도 그릴 수 있고, Storyboard로 이쁘게 레이아웃을 구성할 수도 있고, frame기반으로 계산을 일일이 해서 화려한 에니메이션을 연출 할 수도 있고 말이다.

하지만, 현업에서 자유도도 중요하지만 동료들과의 협업과 생산성도 무시못하기 때문에 동료들과 의견을 수렴해서 모두가 만족하는 UI설계방식 뭐 프레임워크를 잡는데 이 글이 도움이 되길 바란다.

그리고 당근마켓 iOS개발자들은 FlexLayout사용에 있어 가독성과 사용에 있어 생산성을 높이기 위해 KarrotFlex(https://github.com/daangn/KarrotFlex) 를 개발중인데 혹시 관심이 있으시다면 star나 컨트리뷰트도 좋지만 이력서를 지원해보는건 어떻겠습니까?

--

--