TIL(Today I Learned)

12월 5일(일)

학습내용

  • SwiftUI(CS193p Lecture 5)
    • View는 Read-Only이다.
      • Read-Only이므로 외부에서 변환시킬일이 없으므로 View의 프로퍼티는 모두 상수여야 한다. (@State, @ObservedObject를 제외하고)
      • Stateless
    • @State
      • @State 는 View의 상태를 나타내기 위해 사용한다.
        @State private var somethingTemporary: SomeType // SomeType은 struct이여야 한다.
      
      • 접근 제한은 private이여야 한다. (View 내부에서만 사용되어야 하므로)
      • 또한 변수의 타입은 struct type이여야 한다.
      • @State의 변수 값이 변하면 뷰를 재생성한다.
      • @State의 변수는 View의 lifetime 동안 heap에 살아있다.
        • 여기서 lifetime이란, View가 화면에 나타나 있는 동안을 의미한다. (View struct의 lifetime이 아닌)
      • View가 재생성되면 State 변수는 heap에 있는 내용을 다시 point 하게 된다.
      • @State은 “source of truth”이다.
      • @State 사용을 너무 남용하면 안된다.
      • Model에 포함되지 않아야 한다.
    • ViewModel
      • View와 model 사이의 gatekeeper이다.
      • ViewModel은 View에 보여지고 싶은 model을 노출시킨다.
      • ViewModel의 Intent Function을 통해서만 model을 변경할 수 있다. (Intent Function을 통해서 사용자와 상호작용)
    • View
      • View 내부에 non-private var와 func을 선언하는 경우는 드물다. (View가 ephemeral하므로, 즉 오래동안 지속되지 않으므로)
    • Access Control
      • open과 public은 주로 library code에서만 사용된다.
      • internal이 기본이며, app 내부에서 접근이 가능하다.
      • static 함수도 private이 가능하다. (private static)
    • Xcode 변수명 변경방법
      • 변경하려는 변수명을 command 키와 같이 누르면 말풍선이 뜨는데, rename을 클릭한다.
    • Computed Property
      • Computed Property 내부에 get {}, set {}을 구현할 수 있다.
      • set 내부에서 입력 값을 newValue로 참조할 수 있다.

          private var indexOfTheOneAndOnlyFaceUpCard: Int? {
              get { cards.indices.filter({cards[$0].isFaceUp }).oneAndOnly }
              set { cards.indices.forEach { cards[$0].isFaceUp = ($0 == newValue) } }
          }
        
    • Property Observers
      • struct 변수에 할당된 값의 변화를 감지하기 위한 것
      • willSet, didSet
        • willSet - newValue
        • didSet - oldValue
    • Layout
      • SwiftUI는 뷰에 어떻게 공간을 할당하는가? (뷰 크기 결정 과정)
        1. Container View은 하위 뷰에 space을 전달한다.
        2. View는 자기 자신의 크기를 스스로 결정한다.
        3. Container View는 크기가 결정된 하위 뷰를 적절하게 위치시칸다.
        4. 위 과정으로 결정된 크기로 Container View의 크기를 결정한다.
      • HStck, VStack
        • 하위 뷰 중 제일 flexible하지 않은 뷰에 먼저 space을 제공한다.
          • Image(inflexible) : 고정된 크기로 존재하려 한다.
          • Text(slightly more flexible) : 텍스트에 맞는 크기로 존재하려 한다.
          • RoundedRectangle(very flexible) : 남은 공간을 차지한다.
        • 가장 유연하지 않은 뷰부터 공간을 전달하면서 크기를 결정하고, 이후 남은 공간을 다음 유연한 뷰에 전달하는 방식으로 공간이 할당된다. (가장 유연한 뷰가 제일 마지막에 나머지 공간을 차지한다)
        • 내부의 뷰의 크기가 모두 결정되면, 그것들을 감싸는 크기로 Stack의 크기가 결정된다.
        • 만약 스텍 내부에 존재하는 뷰가 모두 very flexible일 경우, 스텍도 마찬가지로 very flexible이 된다.
        • Spacer(minLength: CGFloat)
          • 아무것도 그리지 않는 뷰. 나머지 모든 공간을 차지한다. 빈공간을 만들기 위해 사용
        • Divider()
          • 스텍 내부에 구분선을 만든다. 선을 그리기 위한 최소한의 공간을 차지.
        • .layoutPriority(Double)
          • 어떤 뷰가 공간을 전달받을것인지를 이 우선순위를 통해 바꿀 수 있다. 숫자가 높을수록 우선순위가 높다.
        • alignment
          • 스텍 내부에서 뷰를 어떻게 정렬할 것인가를 결정.
          • .leading vs .left
            • left는 절대적 위치(왼쪽) leading은 기기 언어설정에 맞게 왼쪽 또는 오른쪽이 될 수 있다. (Arabic 또는 Hebrew)
      • LazyHStack, LazyVStack
        • onscreen이 아닌 뷰는 그리지 않는 스텍이다.
        • scrollview내부에 스텍을 넣고 싶을 때 사용한다.
        • 내부에 flexible만 있어도 스텍이 flexible해지지 않는다.
      • ScrollView
      • LazyHGrid and LazyVGrid
      • List and Form and OutlineGroup
        • Smart VStack
      • ZStack
        • 겹쳐지는 뷰
        • 자식뷰에 의해 사이즈가 결정된다.
        • 자식이 flexible이면 ZStack도 flexible해진다.
      • .background modifier
        • 겹쳐진다는 면에서 ZStack과 비슷하지만, 크기가 뷰에 종속적이라는점이 차이점이다.
        • Text(“hello”).background(Rectangle().foregroundColor(.red))일 경우, 사각형 크기는 텍스트의 크기와 동일하다.
      • .overlay modifier
        • 마찬가지로 뷰를 겹치기 위해서 사용된다. (앞에다가 둔다)
        • Circle().overlay(Text(“Hello”), alignment: .center)일 경우, 원 앞면의 중앙에 원 크기와 동일한 크기의 텍스트가 들어가게 된다.
      • Modifiers
        • view를 반환한다는 점에서 container view와 비슷하다.
        • 원래 뷰의 크기 그대로의 뷰를 반환한다.
        • padding
          • 반면에 padding은 뷰의 레이아웃 변화에 관여한다고 할 수 있는데, 여백을 넣고 원래 뷰의 크기를 줄이기 때문이다.
        • aspectRatio
          • 뷰의 가로 세로 비율을 정하기 위한 기능
          • 전달받은 공간보다 더 큰 크기(.fill) 또는 더 작은 크기로 비율을 적용할 수 있다 (.fit)
      • Example(01:03:20 - 01:03:30 )

          HStack {
              ForEach(viewModel.cards) { card in
                  CardView(card: card).aspectRatio(2/3, contentMopde: .fit)
              }
          }
              .foregroundColor(Color.orange)
              .padding(10)
        
        • 공간을 전달 받을 첫번째 뷰(상위 뷰)는 padding 10이 적용된 뷰이며, 상하좌우 모든 면이 10이 감소되고 forgroundColor가 적용된 뷰가 될 것이다.
        • 뷰의 전체 공간을 먼저 HStack에 전달한다
        • ForEach로 생성된, 그리고 aspectRatio가 적용된 뷰에게 HStack이 전달받은 공간을 나누어 전달한다.
        • aspectRatio가 적용된 뷰는 전달받은 공간의 width를 본인의 width로 취하며, 2/3 비율에 맞게 height를 결정한다. (또는 반대로, 높이를 먼저 결정하고 비율에 맞게 너비를 결정)
        • 결국 전체 뷰의 크기는 HStack이 결정한 크기(.aspectRatio 뷰들의 크기 + 10)가 된다.
      • GeometryReader
        • 전달받은 모든 공간을 사용하며, 현재 뷰의 공간의 크기를 알고 싶을 떄 사용한다.
        • ViewBuilder 이며, content에서 proxy를 argument로 전달받는다.
      • SafeArea
        • 아이폰 X 노치
        • .edgesIgnorigSafeArea()로 무시할 수 있다.
      • View 내부에 아래와 같은 방식으로 뷰에서 사용할 상수를 저장할 수 있다.

          struct CardView: View {
              private struct DrawingConstants {
                  static let cornerRadius: CGFloat = 20
                  ...
              }
          }
        
      • @ViewBuilder
        • 기존의 클로저는 swift 코드의 실행을 의미했는데, 이를 뷰의 나열로 바꾸기 위한 문법이다.
        • 함수나 연산 프로퍼티에 @ViewBuilder라고 기재하면 된다.
        • if-else로 조건에 따라 뷰를 변경하는 것이 가능하다.
        • let 사용이 가능하다.
          @ViewBuilder
          func front(of card: Card) -> some View {
              let shape = RoundedRectangle(cornerRadius: 20)
              shape
              shape.stroke()
              Text(card.content)
          }
        

참고