[SwiftUI-기초] NavigationTitle 수동으로 inline 만들기(with. ToolbarItem)
결과물 미리보기
문제 상황
커머스의 상품의 디테일 페이지 같은 경우 스크롤뷰가 있고 그 안에 이미지가 top 부분 safeArea를 넘어 자리하는 경우가 있다.
이 때 타이틀을 어디에 넣어야 적절한지 정하기 까다롭다. 만약, 이미지 왼쪽 하단에 아이템의 타이틀이라도 있으면 불편하다.
이 때도 SwiftUI에서 기본으로 제공하는 것처럼 title display mode를 .large에서 .inline으로 변화하는듯한 UI를 그려보자
일반적인 상황
일반적으로는 .navigationTitle("title")을 하게 되면 색상이나 폰트 등등 커스텀하기가 매우 불편해진다.
대신 타이틀이 어떤 모드로 보일지를 변환하는건 자동으로 된다.
var body: some View {
ScrollView {
VStack {
~~
}
.navigationTitle("제목")
}
.ignoresSafeArea()
}
.navigationTitle("제목")을 제거하고 UI 요소 안에서 제목처럼 보이는 타이틀을 넣는다.
여기까진 좋은데 이 화면이 스크롤 될 때 타이틀이 허전하다.
해결방법
ToolbarItem과 뷰에 onVisibilityChange 뷰 수정자를 만들어서 해결한다.
스크롤을 내리다가 "제목"이 화면에서 안보이면 타이틀을 위치 시킨다.
struct ContentView: View {
@State private var isInline = false
var body: some View {
ScrollView {
VStack {
/// 생략
HStack {
Text("제목")
.font(.title)
Spacer()
}
.padding()
/// 생략
}
.toolbar {
//뒤로가기
ToolbarItem(placement: .cancellationAction) {
Button {
isInline.toggle()
} label: {
Image(systemName: "chevron.left")
}
}
// 네비게이션 타이틀
ToolbarItem(placement: .principal) {
if isInline {
Text("제목")
}
}
}
}
.ignoresSafeArea()
}
}
onVisibleChanged
해당 뷰가 화면에서 보이는지 보이지 않는지를 판단하는 뷰 수정자를 제작
GeometryReader와 Custom ViewModifier를 이용
struct ViewVisibilityModifier: ViewModifier {
let onChange: (Bool) -> Void
func body(content: Content) -> some View {
content
.background(
GeometryReader { geometry in
let minY = geometry.frame(in: .global).minY
Color.clear
.onChange(of: minY) { _ in
let isVisible = UIScreen.main.bounds.intersects(geometry.frame(in: .global))
onChange(isVisible)
}
}
)
}
}
extension View {
func onVisibilityChange(_ onChange: @escaping (Bool) -> Void) -> some View {
self.modifier(ViewVisibilityModifier(onChange: onChange))
}
}
content로 부터 받은 뷰가 화면밖으로 나가는지를 geometryReader로 확인하고 경계를 나가는지를 onChange 클로저를 통해 반환
아래는 View 밑에서 .을 찍어서 사용할 수 있도록 커스텀 뷰 수정자 선언
사용 예시
Text("제목")
.font(.title)
.onVisibilityChange { isVisible in
isInline = !isVisible
}
//생략~
ToolbarItem(placement: .principal) {
if isInline {
Text("제목")
.bold()
}
}
아래로 스크롤을 내리면 제목 텍스트가 발현됨
이렇게 하면 되긴하나 제목이 완전 기기영역을 나가야 타이틀이 생겨서 약간 시점이 맞지 않음
그래서 visiblity를 확인하려는 그 View에 수정자를 다는 것이 아니라, 바로 위의 뷰에 달면 효과적
let clearHeight = 1.0
// 이미지 영역
Color.blue
.frame(height: 300)
.background(
Color.clear
.frame(height: clearHeight) // 이 부분을 수정 가능
.onVisibilityChange { isVisible in
isInline = !isVisible
}
)
.overlay {
Text("This area is a image")
.font(.title)
}
// 타이틀 영역
HStack {
Text("제목")
.font(.title)
Spacer()
}
.padding()
//생략~
ToolbarItem(placement: .principal) {
if isInline {
Text("제목")
.bold()
}
}
백그라운드에 넣었고, Color.clear로 선언을 했기 때문에 Frame Height 크기를 주기 자유로움
각자 UI에서 잘 맞도록 선언을 해야함, 필자의 경우는 200이 적당했음
잘 맞춘 경우
스크롤을 내릴 때 네비게이션 영역이 불투명한 것을 클리어하게 하는 방법
.toolbarBackground(color), .toolbarBackground(visibility)를 동시에 설언
struct ManualInlinView: View {
@State private var isInline = false
private let clearHeight = 200.0 // 이 부분을 수정 가능
var body: some View {
ScrollView {
VStack {
// ~~ 중략
}
.toolbarBackground(.tintColor, for: .navigationBar)
.toolbarBackground(
isInline ? .visible : .hidden,
for: .navigationBar
)
}
.ignoresSafeArea()
}
}
배경색을 아예 안주다가 일정 y축을 지나가면 보이게 설정하는 방법
전체코드
https://gist.github.com/Damagucci-Juice/3863e56455116f0b121f0908033f5303
ManualInlinView.swift
GitHub Gist: instantly share code, notes, and snippets.
gist.github.com