swift - 如何在 SwiftUI Map():View 上正确放置 2000 多个自定义注释以将延迟降至最低
问题描述
当您需要在 SwiftUI 中的 Map 上拥有一堆自定义注释时,我很好奇什么是最佳实践。这是我的第一个真正的 IOS 项目,所以我有点粗略。目前,我有 2400 个注释,它们是带有自定义图像的按钮,最终,当用户单击它们时,它们会弹出有关艺术品的信息。
到目前为止,我所做的是将每个引脚的所有数据放入 CoreData 中,并与它所代表的实体有适当的关系,并在我的资产文件夹中直接从 MapAnnotation 内容中调用一个图像字符串。
我在自己的 iPhone 8 上运行过它。FPS 显着下降。我不确定是不是因为注释的数量很大,但这不会下降。这是我的 MapView() 的示例
仅供参考,我获取了 onAppear() 的引脚,按钮上也将是一个过滤器。代码还远未完成,只是在走得太远而无法返回之前尝试解决一个问题。
感谢您的任何意见。祝你有美好的一天
导入 SwiftUI 导入 MapKit 导入 CoreData
结构地图视图:查看{
@Environment(\.managedObjectContext) private var moc
@State var pins: [Pin] = []
@State var userTrackingMode: MapUserTrackingMode = .follow
// Location for Montreal downtown
@State var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 45.50240, longitude: -73.57067),
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
var body: some View {
ZStack{
Map(coordinateRegion: $region,
interactionModes: MapInteractionModes.all,
showsUserLocation: true,
userTrackingMode: $userTrackingMode,
annotationItems: pins)
{ pin in
MapAnnotation(coordinate: CLLocationCoordinate2D(
latitude: pin.location?.latitude ?? 0,
longitude: pin.location?.longitude ?? 0))
{
Button(action: {
}){
if pin.isArtwork {
Image("\(pin.imageDefault!)")
.resizable()
.scaledToFit()
.frame(width: 10)
} else if pin.isPlace {
Image("\(pin.imageDefault ?? "place_pin")")
.resizable()
.scaledToFit()
.frame(width: 10)
}
}
}
}
.accentColor(Color.blue)
HStack(alignment: .bottom){
Spacer()
VStack(alignment: .trailing){
Spacer()
Button(action: {userTrackingMode = .follow}) {
Image("user_location")
.resizable()
.scaledToFit()
.frame(width: 40.0)
}
.padding()
.padding(.bottom, -20)
.cornerRadius(10)
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
VStack{
Button(action: {
pins = fetchPins(predicate: "isArtwork == false")
}) {
Image("map_filter")
.resizable()
.scaledToFit()
.frame(width: 40.0)
}
.cornerRadius(10)
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
.padding()
.onAppear {
pins = fetchPins(predicate: nil)
}
}
}
.padding(.bottom, 50)
}
}
}
}
扩展地图视图 {
func fetchPins(predicate: String?) -> [Pin]{
do {
let request = Pin.fetchRequest() as NSFetchRequest<Pin>
if predicate != nil && predicate!.count > 0 {
let predicate = NSPredicate(format: predicate!)
request.predicate = predicate
}
return try moc.fetch(request)
} catch {
fatalError("Error fetching pin + predicate")
}
}
}
解决方案
我认为问题在于,每当区域发生变化时,视图就会重新构建,因此 SwiftUI 最终会不断刷新注释列表。
另一个答案消除了滞后,因为当区域更改时我们不再重建视图,但我注意到当注释列表更新时,地图区域被重置为初始值(这是有道理的,因为我们正在使用.constant(region)
)。为了避免这种情况,我做了以下事情。
首先,使用自定义绑定为区域创建一个包装类。
class RegionWrapper {
var _region: MKCoordinateRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 30, longitude: -90),
span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10))
var region: Binding<MKCoordinateRegion> {
Binding(
get: { self._region },
set: { self._region = $0 }
)
}
}
现在,在视图结构中,创建一个实例RegionWrapper
并传递regionWrapper.region
给地图。
这适用于大多数情况,但如果您想以编程方式更改区域,则必须进行一些额外的更改,以便重建视图。
首先,制作RegionWrapper
一个ObservableObject
,并添加一个@Published
标志变量。最后的类应该是这样的:
class RegionWrapper: ObservableObject {
var _region: MKCoordinateRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 30, longitude: -90),
span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10))
var region: Binding<MKCoordinateRegion> {
Binding(
get: { self._region },
set: { self._region = $0 }
)
}
@Published var flag = false
}
将视图结构中的变量更新为@StateObject
. 现在,每当您想更改区域时,首先 set regionWrapper.region.wrappedValue
,然后调用regionWrapper.flag.toggle()
以强制重建视图(您可以将它们包装起来以withAnimation
获得更平滑的过渡)。示例视图如下。
struct MapView: View {
@StateObject private var regionWrapper = RegionWrapper()
var body: some View {
Map(coordinateRegion: regionWrapper.region, annotationItems: annotations) { ... }
}
func updateRegion(newRegion: MKCoordinateRegion) {
withAnimation {
regionWrapper.region.wrappedValue = newRegion
regionWrapper.flag.toggle()
}
}
}
希望这可以帮助遇到此问题的任何人。
推荐阅读
- prefect - 在 Prefect 中禁用结果自动保存任务
- grafana - Grafana Alerts:使用单个查询监控多个 linux 服务
- java - Selenium + Jenkins 集成:初始化插件时出错
- flutter - Flutter 2:更改从父级发送给子级的数据
- mysql - 是否可以为 MySQL 中的表创建数据库范围的别名?
- php - PHP动态输出数组数据
- php - 如何使用 laravel 8 显示保存在数据库中的图像?
- junit4 - JUnit 4 测试结果未在 Eclipse 中显示?
- java - Spring Boot 中 Pojo 中带有 RefreshScope 的 Bean
- c# - 结合使用 AutoMapper Mapping 和 EF Select Query