Apple Developer 문서에 표현된 SwiftUI / View Modifier 관련 탐방
https://developer.apple.com/documentation/swiftui/configuring-views
https://developer.apple.com/documentation/swiftui/viewmodifier
UPDATE : 2023-08-10 23:20
이번에는 SwiftUI로 UI를 표현할 때 사용되는 View들의 Modifier들에 대해 알아보겠습니다.
View
의modifier
를 사용하여 특성을 조정합니다!
SwiftUI에서
UI
는View들을 계층구조 형태로 구성
하여 구현하며, 이때원하는 형태의 UI
로 커스텀하기 위해서 View의modifier
들을 사용합니다.
대표적으로Text
,Image
,Button
처럼 내장된 View와 같이View
프로토콜을 채택하는 View들은 View 프로토콜로 제공되는 Viewmodifier
들을 사용할 수 있습니다.
그리고 View Modifier의 대표 기능들은 아래와 같습니다.
- View에 Voice Over와 같은
accessibility
(손쉬운 사용 / 접근성) 기능을 추가합니다.- View의 스타일, 레이아웃 등 View의
외관 특성
(UI)을 조정합니다.event
가 발생하면 이를 수신받아응답
할 수 있습니다.- 특정 조건에 따라
modal view
(모달창)를 표시할 수 있습니다.toolbar
(도구모음바)와 같은 supporting view들을 구성할 수 있습니다.
https://developer.apple.com/documentation/swiftui/configuring-views#Configure-a-view-with-a-modifier
modifier
는 View의 메소드 형식으로 사용이 가능합니다.
그리고 modifier 기능에 따라 매개변수를 넣습니다.
예를 들어 .foregroundColor(_:)modifier
를 사용하여 View의 색상을 매개변수 값으로 설정할 수 있습니다.
Text("Hello World!")
.foregroundColor(.red)
modifier를 통해 반환되는 View는
기존 View를 대체
하여 계층구조를 유지
합니다.
위의 예시는 빨간색으로 표시되는 Text를 반환하는 것과 같습니다.
SwiftUI는
선언적 접근 방식
을 사용하여 UI를 View의 계층구조로 구현합니다.
계층구조 내에서 해당되는 위치에 View를 선언하여 UI를 구성합니다.
더 자세한 내용은 Declaring a custom view를 참고해주세요.
Declaring a custom view 내용은 SwiftUI / View 글로 정리했습니다!
여기까지 정리를 해볼께요!
먼저 View 프로토콜을 준수하는 내장 View 및 커스텀 View들은 View 프로토콜을 채택하여 사용 가능한 modifier 들이 있고,
이 modifier들의 기능은 UI가 조정된 View를 반환하는 역할, 또는 event 발생을 수신하여 UI업데이트, 그리고 모달창 띄우는 등의 기능들이 있습니다!
예시로 .foregroundColor(_:) mofidier를 통해 View의 색상을 설정할 수 있었죠!
중요한것은 modifier를 통해 반환되는 View는 기존 View를 대체하는 새로운 View를 반환하여 기존 View들의 계층구조를 그대로 유지시켜준다는 점이였습니다!
그리고 modifier를 따로 다룬 이유인 chain 내용을 살펴보겠습니다!
modifier들을 chain 식으로
차례로 호출
하면 modifier로 반환되는 View를 modifier로 반환하는 등 각각이전 결과인 View를 래핑
하는 식으로 chain으로 묶을 수 있습니다!
예시 코드와 같이.frame
modifier 다음에.border
modifier룰 사용하여지정된 width 크기로 외곽선이 표시된 View
를 만들 수 있습니다!
Text("Title")
.frame(width: 100)
.border(Color.gray)
하지만 chain modifiers를 사용할 시 주의할점은
modifier들의 순서가 중요
합니다!
아래 코드처럼
.border
modifier 다음에.frame
modifier를 사용하면 이전 결과와 다르게 표시됩니다.
이는 기존 Text 뷰의
기본 크기에 외곽선이 표시
된 후에전체의 width가 100으로 설정
되는 것으로, 최종적으로 차지하는 공간의 크기는 같지만외곽선이 표시되는 공간의 크기가 달라질 수 있습니다!
Text("Title")
.border(Color.gray)
.frame(width: 100)
View에서 사용가능한 modifier들을 chain 식으로 엮을 수 있었다는 사실!
그리고 modifier들의 순서
가 UI로 표시되는 결과물에 영향을 끼칩니다!
왜냐면 modifier로 반환되는 View에 mofidier가 영향을 끼치는 구조
이므로, 반환되는 순서에 따라 영향이 다르게 나타날 수 있다는 것
입니다!
https://developer.apple.com/documentation/swiftui/configuring-views#Configure-child-views
현재 View에 즉각적인 영향이 없더라도 View 프로토콜에 의해 제공되는 modifier들은 모두 사용할 수 있습니다.
VStack과 같이View Builder
로 묶인 View에 modifier를 적용하면 VStack 자체에는 영향이 없지만child View들에게 모두 modifier 기능이 전파
되어 적용됩니다.
예시 코드처럼
VStack
에.font(_:)
modifier를 적용하면내부 child View들에게 모두 .font(_:)
modifier가 적용된것과 같습니다.
만약 View Builder에 사용된 modifier를 내부 특정 View에서 동일한 modifier를 사용한다면 해당 특정 View는내부에서 재정의된 modifier가 적용
됩니다.
VStack {
Text("Title")
.font(.title) // Override the font of this view.
Text("First body line.")
Text("Second body line.")
}
.font(.body) // Set a default font for text in the stack.
여기까지 보면 VStack과 같이 View Builder를 통해 여러 View들을 묶는 View에 modifier를 적용하면 묶인 child View들에게 모두 modifier가 적용된다는 사실!
그리고 특정 View에서 동일한 modifier를 재정의 하는 경우 재정의된 modifier가 적용된답니다!
https://developer.apple.com/documentation/swiftui/configuring-views#Use-view-specific-modifiers
View 프로토콜 채택으로 많은 View Modifier 들을 사용할 수 있지만,
특정한 View 타입에서만 사용가능한 Modifier
들도 있습니다.
예를 들어.font(_:)
modifier의 경우는모든 View
에서 사용이 가능하지만,.bold()
modifier의 경우Text
타입의 View에서만 사용할 수 있습니다.
그렇기 때문에 아래 예시처럼VStack
에.bold()
modifier를적용할 수 없습니다.
VStack {
Text("Hello, world!")
}
.bold() // Fails because 'VStack' doesn't have a 'bold' modifier.
이 내용은 특정 View에서만 제공되는 Modifier가 있다는 내용입니다!
예를 들어 Text 의 경우만 .bold() modifier를 제공한다는 것이죠!
그런데 말입니다?
이상하게 에러가 나야 하는 코드인데 문제없이 잘 동작되더라구요?
알아보니깐 .bold()
modifier가 총 두가지였다는 사실
!
위 내용에서 말한 .bold()
modifier의 경우 Text 에서만 사용이 가능
하며 Text로 반환
되는 modifier 이지만
VStack과 같이 View에서 사용
가능한 .bold(_ isActive: Bool)
modifier도 있었습니다!
즉, 현재 동작되는 modifier는 엄연하게 다른 modifier
였다는 것이죠!
애플의 말이 틀린건 아닌데 예시 코드인 만큼 다른 mofidier로 예시를 들었다면 안헷갈렸을텐데 말이죠!😅
또한 주의할점은 Text와 같이
특정 View 타입에서만 사용가능
한 modifier일지라도some View 타입으로 반환되는 modifier 이후
에는사용이 불가능
합니다!
예시 코드처럼.padding()
modifier를 사용하면 Text 가 아닌some View
가 반환됩니다.
따라서Text 에서만 사용
가능한.bold()
modifier를사용할 수 없게 되는것
이죠!
Text("Hello, world!")
.padding()
.bold() // Fails because 'some View' doesn't have a 'bold' modifier.
이런 문제가 발생할 수 있으므로
modifier들의 순서를 고려
해야 합니다!
Text 에서만 사용 가능한 .bold() modifier를 먼저 사용한 후 .padding() modifier를 사용하면 문제가 해결되죠!
여기가 modifier에서 가장 중요한 내용이였습니다!
바로 특정 View 타입에서만 제공되는 modifier
도 있으며, modifier의 반환 View 타입
에 따라 다음 modifier
를 사용할 수 없는 상황도 생길 수 있는 것
이죠!
따라서 여러 modifier를 chain 식으로 사용하는 경우 각 modifier들마다 사용 가능한 View 타입
과 반환되는 View 타입을 고려
하여 순서를 배치
해야 합니다!
이 부분이 개인적으로 SwiftUI를 처음 접해보면서 어리둥절했던 부분였어요!ㅎㅎ
https://developer.apple.com/documentation/swiftui/viewmodifier
그러면 View 프로토콜을 채택함으로 인해 제공되는 modifier들은 어떤식으로 정의되어있는지, 그리고 커스텀 modifier는 만들 수 있는지에 대해 살펴보겠습니다!
프로토콜이며
View
, 또는 View의modifier
에 적용하여다른 형태의 View를 반환
하는modifier
ViewModieifer
프로토콜을 채택하여모든 View에서 재사용
할 수 있는modifier를 구현
할 수 있습니다!
아래 예시코드는BorderedCaption 이름의 재사용 가능한 modifier
를 생성하는 코드입니다.
여러 modifier를 결합하여 둥근 사각형으로 둘러싸인 파란색 캡션 텍스트를 반환합니다.
struct BorderedCaption: ViewModifier {
func body(content: Content) -> some View {
content
.font(.caption2)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 1)
)
.foregroundColor(Color.blue)
}
}
만들어진 ViewModifier는 .mofidier(_:)를 통해 적용이 가능하지만, View 프로토콜로 제공되는 다른 일반 modifier들 처럼 사용하기 위해서는
View의 extension
으로modifier 메소드를 정의
하여 사용합니다!
extension View {
func borderedCaption() -> some View {
modifier(BorderedCaption())
}
}
아래 예시코드처럼 재사용 가능한 borderedCaption modifier를 사용하여 파란색 캡션 텍스트로 표시할 수 있습니다!
Image(systemName: "bus")
.resizable()
.frame(width:50, height:50)
Text("Downtown Bus")
.borderedCaption()
이처럼 앱 내에서 자주사용될 것 같은 modifier들의 결합이 있는 경우 ViewModifier 프로토콜을 채택한 struct를 생성하여 만든 후 View의 extension으로 modifier method를 정의하면
저희가 직접 구현한 modifier를 생성하여 사용할 수 있습니다!
View 프로토콜을 채택하면 사용가능한 View Modifier들이 있었습니다!
modifier는 UI를 조정하는 기능과 event 발생을 수신하는 등의 기능들이 있습니다!
그리고 modifier를 통해 반환되는 View는 기존 View를 대체하여 새로운 View가 반환됩니다! 이를 통해 계층구조가 유지되죠!
여기서 가장 주의할점은 여러가지 modifier를 chain 식으로 사용하는 경우 순서를 주의해야 한다는 점 입니다!
modifier를 통해 새로운 View가 반환되고, 해당 View에 새로운 modifier가 연쇄적으로 적용되는 식이므로 UI상으로 결과가 달라질수도 있습니다!
또한 특정 View에서만 사용 가능한 modifier가 있으며, 특정 View 타입으로 반환되는 modifier도 있기 때문에 여러가지 modifier를 chain 식으로 사용하는 경우 View의 반환타입에 따른 순서를 고려해야만 합니다!
그리고 VStack과 같이 child View들을 묶는 View에 modifier를 적용하면 child View들에게 각각 modifier가 적용된 결과와 같아진답니다!
마지막으로 ViewModifier 프로토콜을 채택한 struct를 통해 직접 Modifier를 정의하여 재사용 가능한 modifier를 만들 수 있답니다!
알면 알수록 SwiftUI의 View간 계층구조와 modifier들의 순서 및 결합이 너무나 직관적이라고 느껴집니다!
사용자의 event 또한 modifier로 수신받을 수 있으니 애니메이션이나 UI 전환같은것도 직관적으로 구현할 수 있겠죠? :)