먼저, iOS를 개발하면서 SafeArea를 무시하기 위해 아래와 같은 코드를 사용하곤 할 것이다.
///iOS 14+
.ignoresSafeArea() //전체 무시
.ignoresSafeArea(.all, edges: .top) //top만 무시
///iOS 13까지
.edgesIgnoringSafeArea(.all) //전체 무시
.edgesIgnoringSafeArea(.top) //top만 무시
그런데, 위 코드를 사용하지 않았는 데도, SafeArea을 무시한 적이 없고, 꼭 지켜주고 싶은데, 무시 당했던 적이 몇번 있을 것이다.
그 경우들은 아래와 같다.
Safe Area가 자동으로 무시될 수 있는 경우!
- ScrollView 내부의 컨텐츠 크기가 화면을 초과할 때
- NavigationView 또는 NavigationStack 내부에서 Toolbar나 NavigationTitle이 없는 경우
- ZStack을 사용하여 배경을 깔았을 때
- TabView 내부에서 Safe Area를 고려하지 않고 컨텐츠를 배치할 때
- edgesIgnoringSafeArea(.all)을 사용하지 않았는데도 .background()가 Safe Area를 침범하는 경우
이런 경우 .safeAreaInset()이나 .padding()을 적절히 활용하여 Safe Area를 유지하는 것이 중요하다.
사실, 위에 내용을 해결하려면 아래처럼 그에 맞는 패딩을 생성하거나
//패딩을 생성하는 경우
//방법1
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
//방법2
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.background(Color.gray.ignoresSafeArea(edges: .top))
위 방법1,2말고도 GeometryReader를 사용해서 해결 할 수 있을 것이다.
하지만,
해당 기능이 필요한 화면마다 죄다 뷰 밑에 .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0) 뭐 이런식으로 들어간다면, 사실 매우 지저분 하고, 가독성도 떨어지고,인생이 그렇게 만만하냐? 싶다.
그래서 @ViewBuilder를 사용해서 이를 해결하는 법을 공유하고자 한다.
참고로, @ViewBuilder는 some View를 반환하는 형태로, 조건문을 통해 여러 뷰를 반환 할 수 있게 도와주는 속성(wrapper)이다. 쉽게 말하면, 그냥 SwiftUI의 View에 커스텀할 View들을 만든다고 보면 된다. 그럼이제 SafeArea를 지키러 가보자.
@ViewBuilder 만들기
extension View {
@ViewBuilder func unignoresSafeArea(_ edges: Edge.Set = .top) -> some View {
if edges.contains(.top) || edges.contains(.bottom) {
self.modifier(PaddingsStatusBar(edges: edges))
}
else {
self
}
}
}
View의 extention을 통해 @ViewBuilder로 View를 반환하는 함수를 만드는 것이다. safeArea이 무시되는 함수가 .ignoresSafeArea()인걸 고려하여 앞에 un을 붙여서 통일감을 주었다. 그리고 Edge.Set를 받아서 top의 safeArea을 무시하지 않게, bottom의 safeArea를 무시하지 않게 두가지를 구현하기로 했다. 그리고 해당경우 일 때 modifier로 적용되도록 한다.
ViewModifier 를 준수할 구조체 만들기.
struct PaddingsStatusBar: ViewModifier {
var edges: Edge.Set
func body(content: Content) -> some View {
switch edges {
case .top:
content
.padding(.top, max(0, statusBarHeight - 44))
case .bottom:
content
.padding(.bottom, max(0, statusbottomBarHeight - 30))
default:
content
}
}
}
결국엔 .padding 속성을 이용할 것이고, 결국 상태바만 남겨 놓고 싶은 것이기 때문에 이름을 PaddingsStatusBar로 만들었다.
edges에 따라 body에 content를 반환하도록 작성한다. .top일때는 stateusBarHeight에서 네비높이 값을 뺀만큼, .bottom일때는 stateusBarbottomHeight에서 내가 원하는 높이만큼 빼준다. 심미적인 측면을 고려해 나는 원하는 만큼의 수인 30을 뺐다.
그리고, stateusBarHeight 와 stateusBarbottomHeight은 내가 따로 static으로 선언한 값들이다.
let statusBarHeight = UIApplication.topSafeAreaHeight
let statusbottomBarHeight = UIApplication.bottomSafeAreaHeight
여기서도 UIApplication의 extesion을 이용하여 따로 코드를 빼놓았다.
extension UIApplication {
static var topSafeAreaHeight: CGFloat {
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
return scene?.windows.first?.safeAreaInsets.top ?? .zero
}
static var bottomSafeAreaHeight: CGFloat {
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
return scene?.windows.first?.safeAreaInsets.bottom ?? .zero
}
적용
위의 사항을 모두 만들었다면 이제 사용할 위치에 아래처럼 코드를 삽입하면 된다.

너무 깔끔 만족이다.
가독성도 최고다.
함수명만 읽어도 아~~ 세이프에어리얼 보이게 처리했구나~ 알 수 있지 않은가
해당 코드를 SwiftUI로 만들어진 뷰에 .unignoresSafeArea를 선언하면 SafeArea가 살아있는 것을 볼 수 있다.이제 더이상 SafeArea을 무시할 수 없을 것이다!! 흥흥,, 자랑으로 적용된 화면도 보여주겠음.
- .top

- .bottom

'iOS Dev > SwiftUI' 카테고리의 다른 글
[SwiftUI]@AppStorage 사용해서 Color 값 저장하고 유지하기 (1) | 2025.02.16 |
---|