포스트

JPApexPredators (6)

Map View 사용하기

이전 Mapkit에 이어서…

지금은 지도의 일부만 표시가 되고 있다.

이렇게 일부만 보는게 아니라 지도를 확대해서 조금 더 잘 보이게끔 해보도록 하자

새롭게 파일을 만들고 PredatorMap이라고 명명하였다.

이전글에서는 camera의 distance를 30000으로하고 끝냈는데

이번에는

1
2
3
4
5
6
positoin: .camera(
            MapCamera(
                centerCoordinate: Predators().apexPredators[2].location,
                distance: 1000,
                heading: 250,
                pitch: 80))

이렇게 distance도 줄이고 heading, pitch도 준다.

heading, pitch의 경우는

여기서 언급을 했었기에 패스

현재는 preview에만 적용을 했고

다음과 같다.

Image

MapView에 모든 이미지를 담기위해서

let predators = Predators() 를 만들어 주었다.

그리고 Map안에 Curly brace에 foreach를 사용하여 모든 공룡의 이미지가 나오게 했다

1
2
3
4
5
6
7
8
9
10
11
12
Map(position: $positoin) {
            ForEach(predators.apexPredators) { predator in
                Annotation(predator.name, coordinate: predator.location) {
                    Image(predator.image)
                        .resizable()
                        .scaledToFit()
                        .frame(height: 100)
                        .shadow(color:.white, radius: 3)
                        .scaleEffect(x: -1)
                }
            }
        }

이건 딱히 언급할만한 건 없다.

Image

축소를 하면 이렇게 전부 다 나오는걸 알 수 있다.

여기서 조금 더 응용해서 위성사진 사용을 위한 버튼을 만들어 본다.

우선 변수를 하나 만들고 @State var satellite = false

변수를 만든 이유는 toggle이 되어야 하기 때문.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Map(position: $positoin) {
        // 생략
    }
    .mapStyle(satellite ? .imagery(elevation: .realistic) : .standard(elevation: .realistic))
    .overlay(alignment: .bottomTrailing) {
        Button {
            satellite.toggle()
        } label: {
            Image(systemName: satellite ? "globe.americas.fill" : "globe.americas")
                .font(.largeTitle)
                .imageScale(.large)
                .padding(3)
                .background(.ultraThinMaterial)
                .clipShape(.rect(cornerRadius: 7))
                .shadow(radius: 3)
                .padding()
        }

    }

overlay를 통해 우측 하단에 버튼을 만들어 주고 위와 같이 코드를 작성한다.

여기서는 .mapStyle(satellite ? .imagery(elevation: .realistic) : .standard(elevation: .realistic))에 포커스를 두면 될 것 같다.

  • .mapStyle(...)Map 뷰의 지도 스타일을 설정하는 modifier이다.

  • 이 코드에서는 satellite라는 불리언 상태 값에 따라 다음 두 가지 스타일을 전환함:
    • true일 때: 위성지도(.imagery) 스타일
    • false일 때: 표준지도(.standard) 스타일
  • 두 경우 모두 .elevation(.realistic)이 적용되어, 지형의 고도감을 실제처럼 입체적으로 표현한다.

👉 사용자는 버튼을 눌러 satellite 상태를 토글하며
위성 뷰와 일반 뷰를 전환할 수 있고,
보다 생생한 지도 표현을 경험할 수 있다.

그리고 실행하면 이렇게 된다.

Image

위성사진 로딩이 좀 걸리긴 하네..

이전에는 Detail에서 지도를 탭하면 공룡이 나왔는데

지금 만든 mapview가 나오도록 바꿔주자.

1
2
3
4
5
6
7
8
9
10
NavigationLink {
    PredatorMap(
        positoin: .camera(
            MapCamera(
                centerCoordinate: predator.location,
                distance: 1000,
                heading: 250,
                pitch: 80))
    )
}

간단하다. preview에 작성해둔 코드를 위와같이 navigation link에 옮겨주기만 하면 끝

대신 centerCoordinate: predator.location만 이렇게 다시 바꿔준다.

왜냐면 preview에선 특정값으로 일부러 하드코딩을 해뒀기 때문.

Image

잘 되는걸 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Namespace var namespace

NavigationLink {
    PredatorMap(
        // 생략
    )
    .navigationTransition(.zoom(sourceID: 1, in: namespace))
} label: {
    Map(position: $position) {
       // 생략
    }
    // 생략
}
.matchedTransitionSource(id: 1, in: namespace)

여기서 약간 zoom transition의 효과를 주기위해 navigationTransition과 matchedTransitionSource을 사용했다.

🧭 @Namespace, .matchedTransitionSource, .navigationTransition


🌀 @Namespace

@Namespace는 SwiftUI에서 뷰 간 애니메이션을 연결하고 동기화하기 위해 사용하는 속성 래퍼이다.
서로 다른 뷰 간에 동일한 namespace를 공유하면, SwiftUI가 해당 뷰들의 전환 관계를 인식하고 자연스럽고 부드러운 전환 효과를 적용할 수 있다.


✨ SwiftUI 전환 애니메이션 핵심 개념

SwiftUI에서는 @Namespace를 활용하여 화면 전환 시 부드러운 애니메이션 효과를 만들 수 있다.
이때 사용하는 핵심 modifier는 matchedTransitionSourcenavigationTransition이다.


🔗 matchedTransitionSource(id:in:)

matchedTransitionSource(id:in:)전환의 출발점이 되는 View를 지정하는 modifier이다.

  • id: 전환을 구분하기 위한 고유 식별자
  • namespace: 전환 효과를 공유할 수 있도록 연결해주는 공간
  • 같은 namespace 내에서 navigationTransitionsourceID와 연결됨
  • 이 View의 스타일 변화가 전환 중 애니메이션으로 반영된다

🚀 navigationTransition(_:)

navigationTransition(_:)도착지 View에 적용하는 Modifier로,
전환될 때 어떤 애니메이션 효과를 사용할지 정의한다.

  • 전환 방식: .zoom(sourceID:in:) 등 사용 가능
  • sourceIDmatchedTransitionSourceid와 일치해야 함
  • 동일한 namespace를 공유해야 전환 애니메이션이 자연스럽게 연결됨

⚙️ 실무 팁

  • id 값은 같아도 전혀 문제 없다.
    → 각각 다른 modifier context에서 작동하므로 충돌 없음
  • 중요한 포인트는 @Namespace를 통해 두 View가 같은 전환 공간을 공유해야 한다는 점이다.

✅ 요약

역할적용 위치필요한 요소
matchedTransitionSource출발지 Viewid, namespace
navigationTransition도착지 ViewsourceID, namespace
  • 같은 id + 공유된 namespace = 전환 연결 완성
  • 서로 다른 modifier지만 하나의 전환 흐름을 완성하기 위해 함께 사용됨

Image

이렇게 화면전환이 달라진걸 볼 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.