SwiftUI:用户平移地图后触发视图更新

SwiftUI: Trigger view update after user pans the map

我是 Swift/SwiftUI 的新手,所以如果这是微不足道的,请原谅我。

在一个应用程序中,我试图检索用户的位置,并从维基百科获取附近的网站以显示。下面是一个简单的例子:

//
//  ContentView.swift
//  MRE
//
//  Created by Philipp Maier on 2/25/22.
//

import SwiftUI
import CoreLocationUI
import MapKit
import Foundation

struct ContentView: View {
    
    enum ViewState {
        case waiting, fetching
    }
    
    @State private var viewState = ViewState.waiting
    @StateObject private var viewModel = LocationViewModel()
    
    // some values to initialize
    @State var listEntries = [
        Geosearch(pageid: 45348219,
                  title: "Addison Apartments",
                  lat: 35.21388888888889,
                  lon: -80.84472222222222,
                  dist: 363.7 ),
        Geosearch(pageid: 35914731,
                  title: "Midtown Park (Charlotte, North Carolina)",
                  lat: 35.2108,
                  lon: -80.8363,
                  dist: 1034.5    )
    ]
    
    var body: some View {
        NavigationView{
            VStack{
                ZStack(alignment: .leading){
                    Map(coordinateRegion: $viewModel.mapRegion, showsUserLocation: true)
                         .frame(height: 300)
                    LocationButton(.currentLocation, action: {
                        viewModel.requestAllowLocationPermission()
                        viewState = .fetching
                        print("Button Press: Latitude: \(viewModel.mapRegion.center.latitude), Longitude: \(viewModel.mapRegion.center.longitude)")
                    })
                }
                HStack{
                    Text("Latitude: \(viewModel.mapRegion.center.latitude), Longitude: \(viewModel.mapRegion.center.longitude)")
                }
                
                switch viewState {
                case .waiting:
                    Text("Waiting for your location")
                    Spacer()
                case .fetching:
                    
                    List{
                        ForEach(listEntries) {location in
                            NavigationLink(destination: Text("Target") ) {
                                HStack(alignment: .top){
                                    Text(location.title)
                                }
                            }
                            .task{
                                await fetchNearbyLandmarks(lat: viewModel.mapRegion.center.latitude, lon: viewModel.mapRegion.center.longitude)
                            }
                        }
                    }
                    
                }
            }
        }
    }
    func fetchNearbyLandmarks(lat: Double, lon: Double) async {
        
        let urlString = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=\(lat)%7C\(lon)&gsradius=5000&gslimit=25&format=json"
        
        print(urlString)
        
        guard let url = URL(string: urlString) else {
            print("Bad URL: \(urlString)")
            return
            
        }
        
        do {
            
            let (data, _) = try await URLSession.shared.data(from: url)
            let items = try JSONDecoder().decode(wikipediaResult.self, from: data)
            
            listEntries = items.query.geosearch
            print ("Loadingstate: Loaded")
            
        } catch {
            print ("Loadingstate: Failed")
            
        }
    }
}



final class LocationViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    @Published var mapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40, longitude: -80.5), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
   
    let locationManager = CLLocationManager()
    
    override init() {
        super.init()
        locationManager.delegate = self
    }
    
    func requestAllowLocationPermission() {
        locationManager.requestLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let latestLocation = locations.first else {
            print("Location Error #1")
            return
        }
        
        DispatchQueue.main.async {
            self.mapRegion = MKCoordinateRegion(center: latestLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
        }
       
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Location Error #2:")
        print(error.localizedDescription)
    }
    
}


struct wikipediaResult: Codable {
    let batchcomplete: String
    let query: Query
}

struct Query: Codable {
    let geosearch: [ Geosearch ]
}

struct Geosearch: Codable, Identifiable {
    
    var id: Int { pageid }
    let pageid: Int
    let title: String
    let lat: Double
    let lon: Double
    let dist: Double
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

该应用程序的核心功能有效,一旦用户按下“位置”按钮,下面的维基百科页面列表就会更新。我还添加了文本字段以在用户四处移动时跟踪地图的中心。

这是我的问题:如果我平移地图,下面的位置列表不会更新。然而,一旦我点击 link 显示页面,并点击“后退”按钮,更新的列表就会“正确”构建,即使用新坐标。

我确定我忽略了一些简单的事情,但我如何才能跟踪用户的平移并“实时”动态调整下方的列表?

谢谢,菲利普

对于 Swift/SwiftUI 的新手来说,这很酷!

您想在地图坐标更改时重新获取。所以你可以使用 .onChange, e.g.on 地图的 ZStack:

            .onChange(of: viewModel.mapRegion.center.latitude) { _ in
                Task {
                    await fetchNearbyLandmarks(lat: viewModel.mapRegion.center.latitude, lon: viewModel.mapRegion.center.longitude)
                }
            }

通常这可行,但它可能获取得太频繁(每次更改纬度时)。所以您可能想要添加某种延迟,或者尝试弄清楚拖动何时结束。