SwiftUI / View Modifier

Minsang Kang·2023년 8월 9일
2

SwiftUI

목록 보기
7/12
post-custom-banner

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

Configuring views

이번에는 SwiftUI로 UI를 표현할 때 사용되는 View들의 Modifier들에 대해 알아보겠습니다.

Viewmodifier를 사용하여 특성을 조정합니다!

SwiftUI에서 UIView들을 계층구조 형태로 구성하여 구현하며, 이때 원하는 형태의 UI로 커스텀하기 위해서 View의 modifier 들을 사용합니다.
대표적으로 Text, Image, Button 처럼 내장된 View와 같이 View 프로토콜을 채택하는 View들은 View 프로토콜로 제공되는 View modifier들을 사용할 수 있습니다.
그리고 View Modifier의 대표 기능들은 아래와 같습니다.

  • View에 Voice Over와 같은 accessibility(손쉬운 사용 / 접근성) 기능을 추가합니다.
  • View의 스타일, 레이아웃 등 View의 외관 특성(UI)을 조정합니다.
  • event가 발생하면 이를 수신받아 응답할 수 있습니다.
  • 특정 조건에 따라 modal view(모달창)를 표시할 수 있습니다.
  • toolbar(도구모음바)와 같은 supporting view들을 구성할 수 있습니다.

Configure a view with a modifier

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 내용을 살펴보겠습니다!

Chain modifiers to achieve complex effects

https://developer.apple.com/documentation/swiftui/configuring-views#Chain-modifiers-to-achieve-complex-effects

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가 영향을 끼치는 구조이므로, 반환되는 순서에 따라 영향이 다르게 나타날 수 있다는 것입니다!

Configure child views

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가 적용된답니다!

Use view-specific modifiers

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를 처음 접해보면서 어리둥절했던 부분였어요!ㅎㅎ

ViewModifier

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 전환같은것도 직관적으로 구현할 수 있겠죠? :)

profile
 iOS Developer
post-custom-banner

0개의 댓글