SwiftUI UIViewRepresentable MKMapView с возможностью выбора MKCircles теряет выбор при добавлении нового круга - PullRequest
1 голос
/ 08 марта 2020

Я боролся за то, чтобы SwiftUI работал с MKMapView уже несколько дней. С помощью сообщений здесь

Доступ к элементам MKMapView как UIViewRepresentable в основном (ContentView) представлении SwiftUI

и подобных статьях в Интернете

https://www.hackingwithswift.com/books/ios-swiftui/advanced-mkmapview-with-swiftui

Мне удалось заставить что-то работать. Коснитесь, чтобы выбрать помещенные круги, и выделите их синим цветом, когда они выбраны. Однако проблема, с которой я сталкиваюсь, заключается в том, что выбранный круг теряется всякий раз, когда я добавляю новый круг в массив.

Example App Screenshot

Я загрузил zip тестового приложения для этого здесь ( Извинения за код, мои знания Swift ржавый и мой SwiftUI и MapKit тем более )

Приложение имеет следующую структуру

  • ContentView
    • Основное представление SwiftUI, содержащее мой MapView в zstack с некоторыми кнопками
    • , включая следующие @States
      • @ State private var circleRegions = MKCircle
      • @ State private var selectedCircleIndex: Int?
      • @ Государственный частный var selectedCircle: MKCircle? = nil
  • MapView
    • UIViewRepresentable, который упаковывает MKMapView и имеет координатор для обработки обратных вызовов MKMapView
    • , включая следующие @ привязки к основным переменным состояния ContentView
      • @ Binding var circleRegions: [MKCircle]
      • @ Binding var selectedCircleIndex: Int?
      • @ Binding var selectedCircle: MKCircle?
  • Координатор
    • Координатор, имеющий MapView в качестве родительского свойства и реализующий необходимые методы MKMapViewDelegate, включая наш метод mapTapped

Когда MapView вызывает свой MakeUIView (), он создает MKMapView и устанавливает координатор в качестве отображаемого делегата и добавляет распознаватель жестов касания следующим образом

    mapView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.mapTapped(_:))))

Эта обработка mapTapped выполняется непосредственно до родительского элемента MapView для обработки касания.

После того, как касание перешло в мировое пространство, я проверяю его по circleRegions свойство @Binding массива, чтобы определить, был ли выбран регион. Затем я обновляю свойства selectedCircleIndex и selectedCircle @Binding, чтобы отразить выделение, а затем принудительно перестраиваю наложения MKMapView, чтобы обеспечить создание новых средств визуализации для этих наложений, после чего я могу обновить цвет средств визуализации, чтобы отразить выбор.

Все это прекрасно работает, хотя наложения действительно мигают, когда они удаляются и повторно добавляются, но это не конец света, я не смог найти лучшего способа обновления цвета рендерера, кроме удаления и повторного добавления. добавив все наложения.

Проблема в том, что если я выберу кружок, сделав его синим, а затем немного прокрутите карту, а затем нажмите кнопку кружка, чтобы создать новый кружок в другом месте, то у меня останутся оба круга, отображающиеся красным цветом. последний выбранный, который должен быть синим, все еще "выбран" (выбранный круг и индекс верны), но неправильно отображается красным.

При отладке кажется, что свойство @Binding массива circleRegions в MapView становится недопустимо пустым во время вызов координатора mapView (: rendererFor) MKMapViewDelegate. Который останавливает мой код от сопоставления индекса круга с кругомRegions родителя. Еще более запутанным является тот факт, что если вы поместите точку останова в этот момент в коде, вы сможете go увеличить размер стека до того момента, когда родительский MapView был handleTap (), и вызывая rebuildOverlays (). На этом этапе его circleRegions верны, но к тому времени, когда мы нажимаем на обратный вызов рендерера, они становятся недействительными.

Swift затрудняет понимание того, что родительский MapView координатора является одним и тем же экземпляром в этих разных местах вызова стека, так как вы не можете легко смотреть на значения указателя самого MapView или родителя-координатора в разное время.

Чтобы попытаться отладить или исправить эту проблему, я добавил несколько дополнительных настроек, позволяющих изменить поведение

  • MapView
    • let dispatchMapViewUpdates = false
      • который контролирует, выполняется ли rebuildOverlays () внутри функции обратного вызова распознавателя жестов касания немедленно или отправляется обратно в основную очередь
  • Координатор
    • let testWithIndex = true // обновить это, чтобы проверить с индексом или необязательным кружком
    • , который контролирует, использует ли метод делегата средства отображения карты выбранный индекс или выбранный MKCircle? на родителя, чтобы сказать, выбран ли визуализируемый круг

К сожалению, ни один из этих вариантов не решает проблему, которая просто оставляет меня озадаченным относительно того, что происходит. Я также подтвердил, что эта проблема возникает на устройстве так же, как и в симуляторе, поэтому проблема не только в Simulator SwiftUI.

Если у кого-то есть какие-либо идеи, я определенно буду признателен. Приветствия

...