Как исправить проблемы с выбором аннотаций карты в iOS 17?

Я работаю над MapKit для SwiftUI, используя iOS 17, где у меня есть собственные аннотации. Когда выбрана аннотация, должен отображаться DetailsView, но он не работает должным образом.

  • Выбор аннотации не активирует значок DetailsView, даже если аннотация отображается на карте как выбранная.

Следуя Видео WWDC23: Знакомьтесь с MapKit для SwiftUI в 17:07, я создал специальную аннотацию, которая при выборе увеличивает свой размер, как и ожидалось. Четко указано, что к аннотации должен быть прикреплен tag(_:), чтобы выбор работал.

Но параметр Map(selection:), похоже, не обновляется правильно, чтобы вызвать DetailsView с помощью модификатора sheet(item:_:).

Как я могу гарантировать, что DetailsView сработает должным образом, а также отменит выбор аннотации при закрытии представления?

  • Важно: анимация должна сохраняться при запуске выбранного/невыбранного действия.

Это минималистичный код, который я извлек из своего проекта:

import MapKit
import SwiftUI

struct ContentView: View {

  private var annotations = generateRandomLocations()
  @State private var selection: AnnotationModel?

  var body: some View {
    Map(selection: $selection) {
      ForEach(annotations) { annotation in
        AnnotationMarker(annotation: annotation)
      }
    }
    .sheet(item: $selection) { station in
      DetailsView(id: station.id)
        .presentationDetents([.medium])
    }
  }
}

struct DetailsView: View {
  var id: UUID
  var body: some View {
    Text("DetailsView: \(id)")
  }
}

#Preview {
  ContentView()
}

struct AnnotationMarker: MapContent {

  var annotation: AnnotationModel
  @State private var isSelected = false

  var body: some MapContent {
    Annotation(coordinate: annotation.coordinate) {
      CustomMarker(isSelected: $isSelected)
    } label: {
      Text(annotation.id.uuidString)
    }
    .tag(annotation)
    .annotationTitles(isSelected ? .visible : .hidden)
  }
}

struct CustomMarker: View {

  @Binding var isSelected: Bool

  var body: some View {
    ZStack {
      Circle()
        .frame(width: isSelected ? 52 : 28, height: isSelected ? 52 : 28)
        .foregroundStyle(.green)

      Image(systemName: "house")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: isSelected ? 32 : 16)
        .foregroundStyle(.white)
    }
    .onTapGesture { withAnimation { isSelected.toggle() }}
  }
}

struct AnnotationModel: Identifiable, Hashable {

  let id = UUID()
  var coordinate: CLLocationCoordinate2D {
    CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
  }
  var latitude: Double
  var longitude: Double
}

/// Create random location for testing purposes.
func generateRandomLocations(count: Int = 50) -> [AnnotationModel] {
  return (1...count).map { _ in
    let latitude = Double.random(in: (43.5673...43.6573))
    let longitude = Double.random(in: (3.8176...3.9076))
    return AnnotationModel(latitude: latitude, longitude: longitude)
  }
}

вам, вероятно, понадобится вычисленная привязка для элемента листа, чтобы она не мешала выбору карты.

malhal 27.06.2024 20:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел способ решить эту проблему. Прежде всего, похоже, это ошибка с выбором предметов Map. Если у Annotation есть label, нажатие на label вызовет выбор Annotation, как и ожидалось.

Однако, поскольку заголовок не требуется, хитрость заключается в том, чтобы передать selection: AnnotationModel как Binding в CustomMarker и оттуда управлять выбранным и невыбранным поведением.

Ниже приведен обновленный код с комментариями рядом с добавленными частями:

ContentView там, где Map

struct ContentView: View {

  private var annotations = generateRandomLocations()
  @State private var selection: AnnotationModel?

  var body: some View {
    Map(selection: $selection) {
      ForEach(annotations) { annotation in
        AnnotationMarker(
          annotation: annotation, 
          selection: $selection // Pass the `selection` in the `AnnotationMarker`.
        ) 
      }
    }
    .sheet(item: $selection) { item in
      DetailsView(id: item.id)
        .presentationDetents([.medium])
    }
  }
}

Обновленный Annotation с кастомным View

struct AnnotationMarker: MapContent {

  var annotation: AnnotationModel
  @Binding var selection: AnnotationModel?

  var body: some MapContent {
    Annotation(coordinate: annotation.coordinate) {
      CustomMarker(
        annotation: annotation, // Pass the `annotation` from the `ForEach` to the `CustomMarker `.
        selection: $selection) // Pass the `selection` to the `CustomMarker `.
    } label: {
      Text(String())
    }
    .tag(annotation) // The tag can now be set here.
    .annotationTitles(.hidden)
  }
}

И именно здесь будут обрабатываться действия selection внутри CustomMarker.

struct CustomMarker: View {

  @State private var isSelected = false

  var annotation: AnnotationModel // Pass the `annotation` of this `CustomMarker`.
  @Binding var selection: AnnotationModel? // Defining which `annotation` is selected, if any.

  var body: some View {
    ZStack {
      Circle()
        .frame(width: isSelected ? 52 : 28, height: isSelected ? 52 : 28)
        .foregroundStyle(.green)

      Image(systemName: "house")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: isSelected ? 32 : 16)
        .foregroundStyle(.white)
    }
    .onTapGesture { // If it is the selected `annotation` from the `ForEach`, define the selection.
      selection = annotation
      withAnimation(.bouncy) { isSelected = true }
    }
    .onChange(of: selection) { // If the previous selected `annotation` from the `ForEach` is unselected, perform the changes.
      guard isSelected, $1 == nil else { return } // Avoid having actions on unselected `annotations`.
      withAnimation(.bouncy) { isSelected = false }
    }
  }
}

Другие вопросы по теме