예거's Bicycle for the mind

[iOS] 당겨서 새로고침(Pull to Refresh) 상용앱처럼 구현하는 방법 본문

iOS & Swift

[iOS] 당겨서 새로고침(Pull to Refresh) 상용앱처럼 구현하는 방법

유예거 2022. 2. 5. 19:15

안녕하세요. 예거입니다. 😄

얼마 전에 종료된 프로젝트에서 처음으로 당겨서 새로고침(Pull to Refresh) 기능을 구현해봤습니다.

 

스크롤 뷰(ScrollView) 리스트 형태를 갖는 화면을 간단한 제스처 만으로 새로고침 할 수 있는 기능인데요.

당근마켓이나 인스타그램에서도 습관처럼 사용하는 기술입니다. 오히려 너무 자연스러워서, 없으면 어색할 정도죠.

 

iOS 에서는 이 기능을 어떻게 구현하나 싶었는데, 다행히도(?) 간편하게 구현할 수 있도록 클래스나 프로퍼티들이 준비되어 있더라구요.

 

1️⃣ UIRefreshControl 클래스와 구현 코드

UIRefreshControl 라는 클래스를 사용하면 됩니다.

개요(Overview)를 짧게 읽어보죠.

 

 

우선, UIScrollView 클래스를 포함해서 이 클래스를 상속받는 UITableView, UICollectionView 에서 사용할 수 있습니다.

이 기능을 구현함으로써, 사용자에게 콘텐츠를 새로고침(refresh)할 수 있는 방법(standard way)을 제공하는 거죠.

 

다음은 코드를 보겠습니다.

 

 

코드를 처음 봤을 때는 "엥 진짜 이것만 하면 된다고?" 라고 생각했습니다.

 

스크롤뷰(테이블뷰/컬렉션뷰)가 가지고 있는 refreshControl 프로퍼티에, 위에서 살펴본 UIRefreshControl 클래스의 인스턴스를 주입해줍니다.

그리고 addTarget 메서드로 어떤 이벤트가 들어오면, 어떤 행동(메서드)을 할 것인지 결정해줍니다.

 

 

addTarget 메서드의 파라미터를 살펴보겠습니다.

 

먼저, 메서드의 목표가 되는 타겟을 지정해줘야 합니다.

만약 스크롤뷰컨(테이블뷰컨/컬렉션뷰컨)에서 구현하고 있다면, 자기 자신을 목표로 만들면 되니까 self 를 지정합니다.

 

호출할 메서드는 #selector 키워드 내에 넣어줍니다.

여기에는 순수 Swift 메서드는 들어갈 수 없고 @objc 키워드가 붙은 메서드만 들어갈 수 있음에 유의해주세요.

 

마지막으로, 이벤트는 valueChanged 를 사용해야만 합니다. 필수입니다!

 

그 뒤에는 @objc 키워드를 붙인, 실제 리프레시 코드를 구현해야 합니다.

위 예시에는 주석으로 "Update your content..." 라고 쓰인 부분은 앱마다 다르게 커스터마이징하면 되겠습니다.

보통은 네트워크 통신을 다시 시도해서 받아온 데이터를 갖고 reloadData() 메서드를 호출해주겠죠? 😄

 

여기까지 구현 완료했다고 가정하고, 앱을 실행해보겠습니다.

 

리프레시가 되긴 되는데, 뭔가 허전한 느낌

 

어떤가요? Pull to Refresh 기능이 구현됐습니다.

 

하지만 뭔가 허전하거나, 어색함을 느끼셨다면, 정상입니다.

왜냐면 대부분의 상용앱들은 살짝 다르게 동작합니다.

 

2️⃣ 리프레시 인디케이터가 잠시 고정되도록 만들기

애플의 기본 뉴스앱을 살펴보겠습니다.

 

 

차이점을 아시겠나요?

새로고침이 작동하면, 잠시 동안은 그 자리에 머물러있다가 사라지는 모습을 관찰할 수 있습니다.

당근마켓, 토스, 인스타그램 같은 대부분의 상용앱들이 동일한 모습을 보여줍니다. 잠시 대기합니다.

 

처음 이 차이점을 발견했을 땐 이렇게 생각했어요.

 

상용앱들은 통신으로 받아올 이미지들이나 데이터 크기가 크기 때문에, 의도하지 않아도 리프레시에 시간이 조금 더 소요되니까 저렇게 머무르는 것이고
내 앱은 데이터가 크지 않아서 거의 즉시 통신 결과를 받아오니까 머무르지 않고 바로 종료되는 건가?

 

만약 이 가설이 사실이라고 해도, 빠르게 이뤄지는 통신을 고의로 늦추는 방법은 또 이상하고...

그렇다고 새로고침이 너무 즉시 이루어지면 손맛(?)이 안 사는 느낌이 들었습니다. 🤔

 

그래서 저는 아래와 같은 코드를 넣어서, 새로고침이 잠시 머무르는 효과를 의도적으로 넣어봤습니다.

 

 

새로고침 통신이 성공했을 때, 0.7초 동안 머무른 뒤에 비동기적으로 reloadData() 메서드를 호출해서 콘텐츠를 새롭게 띄워주고, endRefreshing() 메서드를 호출해서 새로고침을 완료하는 것입니다.

 

앱을 실행해보겠습니다.

 

 

이렇게 구현하니까, 상용앱들처럼 리프레시가 제대로 이뤄지는 듯한 인상을 받을 수 있었습니다.

 

3️⃣ 리프레시 인디케이터 커스터마이징

이대로 끝내긴 좀 아쉽더라구요. 이왕 하는 김에 리프레시 인디케이터를 더 꾸며보고 싶었습니다.

 

 

편의상 "리프레시 인디케이터"라고 불렀지만, 실제로는 UIActivityIndicatorView 라는 클래스입니다.

 

 

위의 configureRefreshControl() 메서드는 뷰컨의 viewDidLoad() 내부에서 호출해주는 메서드입니다.

아래 빨간 네모를 친 부분을 봐주세요.

 

refreshControl 프로퍼티에 접근해서 tintColorsystemIndigo 색상으로 변경하고

attributedTitle 프로퍼티에 접근해서 "당겨서 새로고침" 이라는 String 또한 systemIndigo 색상으로 넣어주었습니다.

 

어떻게 변경됐을지 보시죠! 😊

 

 

처음 기본적인 구현만 했을 때에 비해서, 좀 더 앱의 톤앤매너에 맞고 상용앱스러운 새로고침을 만들 수 있었습니다.

 

아직 풀지 못한 숙제가 있다면, 새로고침이 끝나고 리프레시 인디케이터가 사라질 때 Animating 이 되어 있어서, 부드럽게 사라지는데요.

사라지는 와중에 리스트가 올라오면서, 아주 짧은 시간 동안 두 화면이 겹치는 현상이 생기고 있습니다.

 

상용앱에서는 어떻게 대처했는지 살펴봤는데요. 크게 2가지로 나뉘더라구요.

 

- Animating 을 유지한 경우, 사라지는 짧은 시간 동안에 리스트가 그 영역을 침범하지 않게 막아줌 (인스타그램)

- Animating 을 없앤 경우, 사라질 때는 순간적으로 화면에서 사라지고 리스트 또한 바로 위로 올림 (당근마켓)

 

앱마다 UX 컨셉이 다르니까, 회사였다면 이 부분은 기획자와 잘 얘기해보면 좋을 것 같습니다!

 

근데... 어떻게 Animating 을 꺼버린 건지는 잘 모르겠습니다.

이 부분은 좀 더 연구해봐야겠어요! ㅎㅎ


📎 참고 링크

[애플 공식 문서] UIRefreshControl

[애플 공식 문서] UITableView

[애플 공식 문서] UIActivityIndicatorView

[HIG 문서] Refresh Content Controls

[PXD 블로그] Pull to Refresh(당겨서 새로고침) UI와 사례

[Shaun 브런치] ‘Pull to Refresh UI’를 모방한 브랜딩

Comments