적당한 고통은 희열이다

- 댄 브라운 '다빈치 코드' 중에서

Swift iOS 앱 개발/Swift 튜토리얼

[Stanford iOS] Lecture 5. ViewBuilder + Shape + ViewModifier

hongssup_ 2021. 4. 8. 16:47
반응형

SwiftUI Lecture 5. 

  • SwiftUI Access Control
  • @ViewBuilder - What exactly is that argument to ZStack, ForEach, GeometryReader, etc?
  • Shape - What if I wnat to draw my own View rather than construct it from other Views?
  • Animation - Mobile app UIs look pretty bad without animation. Luckily in SwiftUI, animation (almost) comes for free!
  • ViewModifier - What exactly are functions like foregroundColor, font, padding, etc. doing?

 

Memorize - Access Control

access control 접근 제어 : controlling the access that different structs have to each other's vars.  

- public 

- private 

- private set : a real common access control to want to set (available on vars, functions, own internal computer vars ... )

MemoryGame의 cards를 외부에서 막 변경하지 못하도록 private으로 설정하고 싶은데 그러면 외부에서 읽지도 못함. 

private(set) 이용 가능. : setting은 private하되 reading은 아님.     => read Only

외부에서 cards를 보고, UI에 올려 display할 수는 있지만, cards의 속성을 함부로 변경할 수는 없도록. 

private(set) var cards: Array<Card>

MemoryGame 모델의 cards 변수에 private(set)을 지정하여 뷰모델에서 read Only만 가능하도록 설정해준다.

// MARK: - Access to the Model
var cards: Array<MemoryGame<String>.Card> {
    model.cards
}

 

 

@ViewBuilder

- Support "list-oriented syntax" : a simple mechanism for supporting a more convenient syntax for lists of Views. 

- can be applied(tagged) to any function that returns some View

- contents of a @ViewBuilder is a list of Views, and it builds them into a single View

 

View를 리턴하는 모든 함수에 @ViewBuilder를 태그해줄 수 있는데,

그러면 컴파일러가 그 속의 content들을 뷰 리스트로 해석하고 하나로 결합해준다.

여기서 결합되는 하나의 뷰는 TupleView (2~10 Views) 가 될수도 있고, _ ConditionalContent View (if else가 존재하는 경우) 혹은 아무것도 없는 EmptyView일수도 있다. 

 

@ViewBuilder가 태그된 함수나 변수의 contents들은 뷰의 리스트로 해석된다.  

@ViewBuilder
func front(of card: Card) -> some View {
    RoundedRectangle(cornerRadius: 10)
    RoundedRectangle(cornerRadius: 10).stroke()
    Text(card.content)
}

위의 함수는 TupleView<RoundedRectangle, RoundedRectangle, Text> 를 반환한다. 

 

아래와 같이 View를 리턴하는 parameter에도 @ViewBuilder를 사용할 수 있다. (ex. GeometryReader)

struct GeometryReader<Content> where Content: View {
    init(@ViewBuilder content: @escaping (GeometryProxy) -> Content) { ... }
}

content parameter는 View를 반환하는 함수이기 때문에 @ViewBuilder를 표시해줄 수 있고, GeometryReader 안의 내용들은 list syntax 형태로 사용할 수 있음.

ZStack, HStack, VStack, ForEach, Group에도 모두 동일하게 적용된다.  

 

Shape

: a protocol that inherits from View (Shape은 View를 상속한 프로토콜. 즉, 모든 Shape은 View)

 

- SwiftUI Shape에는 RoundedRectangle, Circle, Capsule 등이 있다. 

이들은 보통 .stroke()나 .fill() 함수에 Color 인자를 받아서 그려지는 것 처럼 보였지만,

사실 fill() 함수를 살펴보면 다음과 같이 'generic function' 이라는 것을 알 수 있다.

func fill<S>(_ whatToFillWith: S) -> View where S: ShapeStyle

여기서 S는 ShapeStyle 프로토콜을 따르는 어떤 것이든 될 수 있다. (Color, ImagePaint, AngularGradient, LinearGradient 등)

 

- How to create my own Shape?  

"path in rect"라는 함수를 이용하면 내가 원하는 Shape을 직접 그릴 수 있다. 

func path(in rect: CGRect) -> Path {
    return a Path 
}

line to, arc, bezier curve 등 내가 원하는 Shape을 그릴 수 있는 Path를 반환해주는 함수로, 드로잉을 지원하는 수많은 기능들을 가지고 있다. 

 

실습) Memorize - create custom shape Pie using path

 

ViewModifier

Animation

Animation은 모바일 UI에서 굉장히 중요한 요소이기 때문에 SwiftUI에서는 굉장히 쉽게 구현할 수 있도록 만들어졌다. 

Animation을 하는 방법에는 크게 Shape을 animate하는 방법과 View를 animate하는 방법이 있는데,  

- Shapes animate themselves directly

- Views are animated via their ViewModifiers

그렇기 때문에 뷰에 애니메이션을 주는 법을 이해하기 위해서는 ViewModifier에 대해 먼저 알아야 한다.   

 

What is ViewModifier? 

- modify a View and return a new View

우리가 즐겨 쓰는 aspectRatio, padding, font, foregroundColor 등 View를 modify하는 모든 기능들을 ViewModifier라고 한다.  

예를 들어, .aspectRatio(2/3)은 사실 .modifier(AspectModifier(2/3))같은 거라고 보면 된다.

- the ViewModifier protocol has one function in it. 

- it's only job is to create a new View based on the thing passed to it. 

protocol ViewModifier {
    associatedtype Content // : protocol's version of a "don't care" type
    func body(content: Content) -> some View {
    	return some View that represents a modification of content
    }
}

the only function in ViewModifier : body

body는 don't care type의 Content 인자를 받아서 some View를 반환한다.  

 

Cardify ViewModifier

Text("👻").modifier(Cardify(isFaceUp: true)) // eventually .cardify(isFaceUp: true)
struct Cardify: ViewModifier {
    var isFaceUp: Bool
    func body(content: Content) -> some View {
        ZStack {
            if isFaceUp {
                RoundedRectangle(cornerRadius: 10).fill(Color.white)
                RoundedRectangle(cornerRadius: 10).stroke()
                content
            } else {
                RoundedRectangle(cornerRadius: 10)
            }
        }
    }
}

.modifer()는 ZStack 안의 내용들을 구현한 새로운 View를 반환한다. 

이런식으로 새로운 뷰에 계속해서 modifier를 주고, modifications가 빠르게 반복되면 animation을 보여줄 수 있다.

 

extension View {
    func cardify(isFaceUp: Bool) -> some View {
        return self.modifier(Cardify(isFaceUp: isFaceUp))
    }
}

이렇게 View에 extention으로 cardify 함수를 선언해주면, 다음과 같이 더욱 간결하게 사용할 수 있다. 

Text("👻").cardify(isFaceUp: true)

 

 

 

Memorize - Create ViewModifier 'Cardify' & tag @ViewBuilder to the body of CardView

새로운 SwiftFile에 ViewModifier 'Cardify' 를 따로 만들어주고 

View에 extension으로 cardify함수를 선언해줘서 .cardify(isFaceUp: _) 으로 간편하게 사용할 수 있도록 해준다. 

그런 다음 CardView의 body에 매치된 카드는 사라지도록? 만들기 위해 if문을 추가해준다.  

@ViewBuilder
private func body(for size: CGSize) -> some View {
    // ViewBuilder: function that could build these complicated View
    if card.isFaceUp || !card.isMatched {
        ZStack {
            Pie(startAngle: Angle.degrees(0-90), endAngle: Angle.degrees(110-90), clockwise: true)
                .padding(5).opacity(0.4)
            Text(card.content)
                .font(Font.system(size: fontSize(for: size)))
        }
        .cardify(isFaceUp: card.isFaceUp)
    }
}

some View를 리턴하는 함수에 @ViewBuilder를 선언해주면 아래 내용들이 다 뷰 리스트들로 해석되고, 굳이 return 값을 따로 설정해주지 않아도 된다. if 조건에 부합할 경우 ZStack 안의 내용들을 뷰 리스트로 해석하여 하나의 뷰로 구현해줄 것이고, if 조건에 부합하지 않는다 하더라도 자동으로 Empty View로 해석되기 때문에 따로 리턴값을 설정해주지 않아도 된다.   

 

 

 

참고 : Lecture5

728x90
반응형