관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] 튜토리얼 - 5. 리스트, 데이터 바인딩 (List, onAppear, offSet, ignoresSafeArea, ScrollView) 본문

iOS 튜토리얼 (SwiftUI)

[iOS - SwiftUI] 튜토리얼 - 5. 리스트, 데이터 바인딩 (List, onAppear, offSet, ignoresSafeArea, ScrollView)

jake-kim 2022. 7. 7. 23:33

* 애플 튜토리얼의 프로젝트 파일을 받아서 실습하고 그 중에서 중요한 부분만 포스팅

4강에서 만든 Row

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
      HStack {
        landmark.image
          .resizable()
          .frame(width: 50, height: 50, alignment: .leading)
        Text(landmark.name)
        Spacer() // <-
      }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
      Group { // <-
        LandmarkRow(landmark: landmarks[0])
          .previewLayout(.fixed(width: 300, height: 70))
        LandmarkRow(landmark: landmarks[1]) // <-
          .previewLayout(.fixed(width: 300, height: 70))
      }
    }
}

List 사용 방법

  • List 선언
    • List의 첫번째 파라미터에는 배열을 주입
    • List의 두번째 파라미터에는 각 배열에 해당되는 id를 keyPath로 주입 (즉, Model을 정의할땐 Identifier 타입을 따르고 있어야, List에 데이터를 주입할때도 용이)
    • 클로저를 통해서 각 배열의 원소 접근
import SwiftUI

struct LandmarkList: View {
  var body: some View {
	// 주입되는 landmarks는 전역으로 선언된 mock 데이터
    List(landmarks, id: \.id) { landmark in
      
    }
  }
}

struct LandmarkList_Previews: PreviewProvider {
  static var previews: some View {
    LandmarkList()
  }
}
  • LandmarkRow 생성자에 landmark 데이터 주입
struct LandmarkList: View {
  var body: some View {
    List(landmarks, id: \.id) { landmark in
      LandmarkRow(landmark: landmark) // <-
    }
  }
}
  • List의 디폴트 값
    • List는 자동으로 내부에 contentInset을 주어서 List와 내부 row간의 간격이 존재
    • 각 Row밑에 separator가 자동으로 생성

데이터 바인딩

  • $를 붙여서 데이터 바인딩
  • 예제) MapView에서 region이라는 데이터가 변경될때마다 맵의 중앙 좌표가 변경되도록 처리
  • MapView 생성
import SwiftUI
import MapKit

struct MapView: View {
    var body: some View {
      Text("init")
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
      MapView()
    }
}
  • Map이라는 뷰를 사용하고 생성자의 파라미터 값으로는 `Binding<MKCoordinateRegion>`이 필요
import SwiftUI
import MapKit

struct MapView: View {
  var body: some View {
    Map(coordinateRegion: <#T##Binding<MKCoordinateRegion>#>) // <-
  }
}
  • 바인딩하기 위해서 @State를 붙인 프로퍼티 선언
struct MapView: View {
  @State private var region = MKCoordinateRegion() // <-
  var body: some View {
    Map(coordinateRegion: <#T##Binding<MKCoordinateRegion>#>)
  }
}
  • 바인딩은 `$`를 앞에 붙여서 사용
struct MapView: View {
  @State private var region = MKCoordinateRegion()
  var body: some View {
    Map(coordinateRegion: $region) // <-
  }
}
  • 또 맵뷰가 appear될때 맵의 위치를 이동시켜주어야 하므로, 초기화 값을 사용하기 위해 coordinate 프로퍼티 선언
struct MapView: View {
  var coordinate: CLLocationCoordinate2D  // <-
  @State private var region = MKCoordinateRegion()
  var body: some View {
    Map(coordinateRegion: $region)
  }
}
  • .onAppear를 이용하여 화면이 appear될 때 region이 입력되도록 설정
struct MapView: View {
  var coordinate: CLLocationCoordinate2D
  @State private var region = MKCoordinateRegion()
  var body: some View {
    Map(coordinateRegion: $region)
      .onAppear { // <-
        region = MKCoordinateRegion(
          center: coordinate,
          span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
        )
      }
  }
}

네비게이션에 사용할 DetailView 구현

  • DetailView 추가
import SwiftUI

struct LandmarkDetail: View {
    var body: some View {
        Text("Hello, World!")
    }
}

struct LandmarkDetail_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkDetail()
    }
}
  • DetailView는 컨텐츠가 많이 들어갈 것이므로, ScrollView를 사용
struct LandmarkDetail: View {
  var landmark: Landmark
  
  var body: some View {
    ScrollView {
      Text("Hello World")
    }
  }
}

* ScrollView는 내부 컨텐츠의 크기만큼 영역 차지

  • MapView추가하고 ignoreSafeArea를 통해 위쪽 safeArea를 침범하지 않도록 설정

struct LandmarkDetail: View {
  var landmark: Landmark
  
  var body: some View {
    ScrollView {
      MapView(coordinate: landmark.locationCoordinate)
        .ignoresSafeArea(edges: .top) // <-
        .frame(height: 300)
    }
  }
}
  • CircleImage를 추가하고 offset을 통해서 맵 위에 이미지가 얹혀지는 형태로 구현

struct LandmarkDetail: View {
  var landmark: Landmark
  
  var body: some View {
    ScrollView {
      MapView(coordinate: landmark.locationCoordinate)
        .ignoresSafeArea(edges: .top)
        .frame(height: 300)
      
      CircleImage(image: landmark.image)
        .offset(y: -130)
    }
  }
}
  • CircleImage는 위로 올라온것처럼 보이지만, 차지하고 있는 영역은 기존 위치를 차지하고 있는 중

  • padding을 통해 아래 130만큼 채워주면 해결

      CircleImage(image: landmark.image)
        .offset(y: -130)
        .padding(.bottom, -130) // <-
  • 그림 하위에 설명을 표출하기위해, VStack과 HStack을 이용하여 처리

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
  • 너무 딱 달라붙어 있으므로, VStack에 padding()을 적용

            VStack(alignment: .leading) {
                ...
                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding() // <-

* 참고

https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation

Comments