본문 바로가기

프로젝트/Twitter Clone App (w∕Firebase)

03. 기본 UI 구성하기 #3

기본 UI 구성하기 #3
ProfileView


ProfileView
| tweetFilterBar

향후 해당 계정의 Tweet을 불러올 때 사용할 버튼을 구상한다.
세 개의 항목이 존재하고, 바로 아래에는 밑줄과 같은 인디케이터가 존재하는 것이 특징이다.

더보기

Source

var tweetFilterBar: some View {
    HStack {
        ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
            VStack {
                Text(item.title)
                    .font(.subheadline)
                    .fontWeight(selectedFilter == item ? .semibold : .regular)
                    .foregroundColor(selectedFilter == item ? .black : .gray)

                if selectedFilter == item {
                    Capsule()
                        .foregroundColor(Color(.systemBlue))
                        .frame(height: 3)
                        .matchedGeometryEffect(id: "filter", in: animation)
                } else {
                    Capsule()
                        .foregroundColor(Color(.clear))
                        .frame(height: 3)
                }
            }
            .onTapGesture {
                withAnimation(.easeOut) {
                    self.selectedFilter = item
                }
            }
        }
    }
    .overlay {
        Divider().offset(x: 0, y: 16)
    }
}

세 개의 메뉴가 나란히 배치되기 때문에 HStack을 사용한다.
ForEach를 사용해 미리 작성해 둔 메뉴를 열거해 메뉴들을 구성한다.

선택 Animation에는 matchedGeometryEffect가 사용됐다.

@Namespace var animation

해당 modifier는 animation을 적용할 View를 Group화 하는 것으로,
위와 같은 @Namespace 안에 'id'로 그룹화한다.

matchedGeometryEffect가 적용된 메뉴의 indicator 역할을 하는 Capsule View들은
오른쪽과 같이 각각의 View가 아닌 마치 하나의 View처럼 Animation이 동작하는 것을 볼 수 있다.

ProfileView
| tweetsView

tweetFilterBar 아래에 위치하는 tweetsView는 이후 tweetFilterBar의 선택에 따라
해당 사용자의 Tweet을 Filtering 해 표시하게 된다.

더보기

Source

var tweetsView: some View {
    ScrollView {
        LazyVStack {
            ForEach(0 ... 9, id: \.self) { _ in
                TweetRowView()
                    .padding()
            }
        }
    }
}

구현 내용은 FeedView의 것과 같다.

 

코드 작성 방식

tweetFilterBar와 같이 동일한 형태를 가진 여러 View가 필요한 경우
이를 일일이 추가하는 것은 당장은 편할지 몰라도 장기적으로 매우 비효율적인 방식이다.
수정사항이 생기는 경우 흩어져 있는 View를 각각 수정해야 하며, 코드의 양도 많아 가독성도 떨어진다.

그래서 영상에서는 현명한 개발자가 되길 바라며 다음과 같은 방식을 소개한다.

var tweetFilterBar: some View {
    HStack {
        ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
            VStack {
                Text(item.title)

실제 지금 구현한 tweetFilterBar는 View를 일일이 추가하지 않고 ForEach를 사용해 열거하는 방식을 사용하고 있다.
그리고 ForEach에 전달되는 것이 TweetFilterViewModel이다.

enum TweetFilterViewModel: Int, CaseIterable {
	case tweets
	case replies
	case likes
	
	var title: String {
		switch self {
		case .tweets: return "Tweets"
		case .replies: return "Replies"
		case .likes: return "Likes"
		}
	}
}

TweetFilterViewModel은 메뉴의 각각의 제목에 해당하는 enumerate 형식의 데이터로,
특징은 CaseIterable protocol을 채용했다는 점이다.
해당 프로토콜의 가장 큰 장점은 allCases를 사용해 enum의 전체 case 데이터에 접근할 수 있다는 점이다.
즉, 해당 protocol을 enum이 채용하면서 ForEach에 이를 전달할 수 있게 됐다는 것이다.

또한 title 속성은 현재 선택된 case에 따라 적절한 문자열을 반환하도록 구현돼 있어 View와는 완전히 모듈화가 돼 있다.

이러한 구현 패턴은 영상의 개발자가 선호하는 방식이고 아마 다른 구현 패턴도 존재할 것이다.
다만 이후에 구현되는 SideMenue의 구현에도 동일하게 사용되니 알아두자.

'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글

05. 기본 UI 구성하기 #5  (0) 2022.11.30
04. 기본 UI 구성하기 #4  (0) 2022.11.24
02. 기본 UI 구성하기 #2  (0) 2022.11.22
01. 기본 UI 구성하기 #1  (0) 2022.11.21
00. 시작하며  (0) 2022.11.03