首页 > 解决方案 > 如何在 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")
    }
}

}


标签: swiftdictionaryswiftuimkannotation

解决方案


我认为问题在于,每当区域发生变化时,视图就会重新构建,因此 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()
        }
    }
}

希望这可以帮助遇到此问题的任何人。


推荐阅读