소개
iOS 기본 앱인 Safari 브라우저(아이폰)을 보면 밑에 있는 URL 도구창 + 툴바가 스크롤 상태에 따라 변하는 모습을 볼 수 있습니다. 애니메이션 및 어떻게 돌아가는지 분석해보려고 합니다.
이번 포스트는 URL 도구창 + 툴바가 확대/축소하는 과정만 알아보겠습니다.
동작 분석
하단의 상태는 크게 둘로 나뉩니다.
-
상태 1: URL 도구창이 확대되었고 툴바가 보이는 상태
-
상태 2: URL 도구창이 축소되었고 툴바가 가려진 상태
그리고 스크롤 뷰의 동작에 따라 위의 두 상태중 하나가 됩니다.
(1) URL 입력창과 툴바가 보이는 상태에서 아래로 스크롤 및 확대를 한 경우
- 툴바가 사라지고, 도구창이 축소됩니다.
- 스크롤뷰를 확대한 경우는 따로 찍지 않았지만 위와 동작이 동일합니다.
(2) URL 입력창과 툴바가 숨겨진 상태에서 위로 스크롤한 경우
- 축소되었던 도구창이 다시 확대되고, 툴바가 다시 나타납니다.
(3) URL 입력창과 툴바가 숨겨진 상태 + 최하단까지 스크롤된 상태에서 한 번 더 아래로 스크롤 한 경우
- 스크롤 맨 아래까지 스크롤 했을 때, 첫번째에서는 축소된 상태에서 따로 반응이 없습니다.
- 이 상태에서 한번 더 아래로 스와이프하면 도구창이 확대되고 툴바가 나타납니다.
구현
Step 1: SwiftUI로 사파리 하단 부분을 최대한 비슷하게 그리기
struct LikeSafariView: View { // ... 상태변수 추가 ... // // 하단 툴바 배경색 강제적용 init() { let toolbarAppearance = UIToolbarAppearance() toolbarAppearance.configureWithOpaqueBackground() toolbarAppearance.backgroundColor = .clear toolbarAppearance.shadowColor = .clear UIToolbar.appearance().standardAppearance = toolbarAppearance UIToolbar.appearance().scrollEdgeAppearance = toolbarAppearance } /// URL 창: 확대된 상태 private var urlLargeArea: some View { VStack(spacing: 0) { // 테두리(위) Rectangle() .fill(Color(white: 238/255)) .frame(height: 1) HStack { // 하얀색 둥근 사각형 RoundedRectangle(cornerRadius: 10) .fill(Color(white: 253/255)) .frame(height: 50) .padding() .shadow(color: .init(red: 0.8, green: 0.8, blue: 0.8), radius: 10, y: 5) .overlay { // 둥근 사각형 안의 내용 HStack { Image(systemName: "character") Spacer() // URL 부분 HStack { Image(systemName: "lock.fill") .foregroundStyle(.gray) Text("en.m.wikipedia.org") } Spacer() Image(systemName: "arrow.clockwise") } .padding(30) } } .padding(.bottom, -15) .background(Color(white: 247/255)) // ...트랜지션 추가... } } /// URL 창: 축소된 상태 private var urlShrinkedArea: some View { ZStack(alignment: .center) { // 배경색 Color(red: 247/255, green: 247/255, blue: 247/255).ignoresSafeArea() VStack(spacing: 0) { // 테두리(위) Rectangle() .fill(Color(white: 238/255)) .frame(height: 1) HStack(alignment: .center) { Image(systemName: "lock.fill") .foregroundStyle(.gray) Text("en.m.wikipedia.org") } .padding(.top, 15) .font(.system(size: 15)) } } .frame(height: 10) // ...트랜지션 추가... } /// Body var body: some View { NavigationStack { VStack(spacing: 0) { // ... 메인 부분 ... } // 툴바 그리기 .toolbar { ToolbarItemGroup(placement: .bottomBar) { HStack { Button {} label: { Image(systemName: "chevron.left") } Spacer() Button {} label: { Image(systemName: "chevron.right") } Spacer() Button {} label: { Image(systemName: "square.and.arrow.up") } Spacer() Button {} label: { Image(systemName: "book") } Spacer() Button {} label: { Image(systemName: "square.on.square") } } .padding(10) .padding(.top, 22) } } } } }
- SwiftUI의 기본 컴포넌트와 SF Symbol 등을 활용해 최대한 비슷하게 흉내내봅니다.
- 분량상 자세한 설명은 생략합니다.
Step 2: 하단바 상태를 토글할 상태변수(@State
) 추가
struct LikeSafariView: View { @State private var showToolBar = true // ... // }
showToolBar
: Bool 값에 따라true
인 경우 확대모드,false
인 경우 축소모드로 사용합니다.
Step 3: showToolBar
에 따라 하단바 토글하는 부분 추가
struct LikeSafariView: View { @State private var showToolBar = true /// URL 창: 확대된 상태 private var urlLargeArea: some View { // ... // } /// URL 창: 축소된 상태 private var urlShrinkedArea: some View { // ... // } /// Body var body: some View { NavigationStack { VStack(spacing: 0) { VStack(alignment: .leading) { Text("WebKit View가 들어갈 예정") .padding(.top, 20) .font(.title) Text("- WebKitView를 Representable로 연결") Text("- 스크롤 상태를 뷰모델을 통해 받아옴") Text("- 스크롤 상태에 따라 URL 창 크기 조절 및 \n툴바의 표시 여부 결정") Divider() Button("State Toggle") { showToolBar.toggle() } } .padding([.leading, .trailing], 20) Spacer() if showToolBar { urlLargeArea } else { urlShrinkedArea } } // 툴바(맨 아래 아이콘 5개 부분) 보이기 여부 .toolbar(showToolBar ? .visible : .hidden, for: .bottomBar) // 툴바 그리기 .toolbar { ... } } } }
"State Toggle"
버튼을 누르면showToolBar
가 토글됩니다.showToolBar
의 값 여부에 따라 어떻게 보여질지 정해지게 됩니다.true
인 경우urlLargeArea
뷰를 표시하고, 툴바를 보여줍(.visible
)니다.false
인 경우urlShrinkedArea
뷰를 표시하고, 툴바를 가립(.hidden
)니다.
Step 4: 애니메이션(Transition, withAnimaiton) 추가 [전체 코드]
import SwiftUI struct LikeSafariView: View { @State private var showToolBar = true // 하단 툴바 배경색 강제적용 init() { let toolbarAppearance = UIToolbarAppearance() toolbarAppearance.configureWithOpaqueBackground() toolbarAppearance.backgroundColor = .clear toolbarAppearance.shadowColor = .clear UIToolbar.appearance().standardAppearance = toolbarAppearance UIToolbar.appearance().scrollEdgeAppearance = toolbarAppearance } /// URL 창: 확대된 상태 private var urlLargeArea: some View { VStack(spacing: 0) { // 테두리(위) Rectangle() .fill(Color(white: 238/255)) .frame(height: 1) HStack { // 하얀색 둥근 사각형 RoundedRectangle(cornerRadius: 10) .fill(Color(white: 253/255)) .frame(height: 50) .padding() .shadow(color: .init(red: 0.8, green: 0.8, blue: 0.8), radius: 10, y: 5) .overlay { // 둥근 사각형 안의 내용 HStack { Image(systemName: "character") Spacer() // URL 부분 HStack { Image(systemName: "lock.fill") .foregroundStyle(.gray) Text("en.m.wikipedia.org") } Spacer() Image(systemName: "arrow.clockwise") } .padding(30) } } .padding(.bottom, -15) .background(Color(white: 247/255)) .transition(.offset(y: 100)) } } /// URL 창: 축소된 상태 private var urlShrinkedArea: some View { ZStack(alignment: .center) { // 배경색 Color(red: 247/255, green: 247/255, blue: 247/255).ignoresSafeArea() VStack(spacing: 0) { // 테두리(위) Rectangle() .fill(Color(white: 238/255)) .frame(height: 1) HStack(alignment: .center) { Image(systemName: "lock.fill") .foregroundStyle(.gray) Text("en.m.wikipedia.org") } .padding(.top, 15) .font(.system(size: 15)) } } .frame(height: 10) .transition(.offset(y: -75)) } /// Body var body: some View { NavigationStack { VStack(spacing: 0) { VStack(alignment: .leading) { Text("WebKit View가 들어갈 예정") .padding(.top, 20) .font(.title) Text("- WebKitView를 Representable로 연결") Text("- 스크롤 상태를 뷰모델을 통해 받아옴") Text("- 스크롤 상태에 따라 URL 창 크기 조절 및 \n툴바의 표시 여부 결정") Divider() Button("State Toggle") { withAnimation(.bouncy(duration: 0.2, extraBounce: -0.2)) { showToolBar.toggle() } } } .padding([.leading, .trailing], 20) Spacer() if showToolBar { urlLargeArea } else { urlShrinkedArea } } // 툴바(맨 아래 아이콘 5개 부분) 보이기 여부 .toolbar(showToolBar ? .visible : .hidden, for: .bottomBar) // 툴바 그리기 .toolbar { ToolbarItemGroup(placement: .bottomBar) { HStack { Button {} label: { Image(systemName: "chevron.left") } Spacer() Button {} label: { Image(systemName: "chevron.right") } Spacer() Button {} label: { Image(systemName: "square.and.arrow.up") } Spacer() Button {} label: { Image(systemName: "book") } Spacer() Button {} label: { Image(systemName: "square.on.square") } } .padding(10) .padding(.top, 22) } } } } }
showToolBar
토글시withAnimaiton {...}
으로 감싸 애니메이션이 동작하도록 합니다.- URL 도구창 뷰마다
transition(.offset(y:))
를 추가해 세로축으로 애니메이션 되도록 합니다.
스크린샷
다음 포스트에서는 WebKitView
를 UIRepresentableView
로 추가하고 위의 SwittUI 뷰와 연동하여 스크롤 상태에 따라 하단 바를 변형시키는 과정에 대해 다루겠습니다.
사실 애니메이션이 그렇게 똑같아 보이진 않긴 한데 계속 분석해서 더 비슷해지면 포스트를 업데이트하겠습니다.
0개의 댓글