SwiftUI中@Published变量无法作为@Binding持久传递的问题
问题分析与解决方案
核心问题
你遇到的shotCoord和ballCoord返回后重置为nil的根本原因是:ClubDetailView内部创建了独立的LocationManagerModel实例,按钮点击时调用的是这个局部实例的currentLocation方法,修改的是局部实例的坐标变量,而非ContentView中共享的那个实例的变量。而waiting是通过@Binding直接绑定到ContentView的共享实例,所以能正常工作。
修复步骤
- 移除
ClubDetailView内的局部LocationManagerModel实例,改为从外部传入共享实例 - 取消
shotCoord/ballCoord的@Binding绑定,直接通过共享的LocationManagerModel实例访问这些@Published变量 - 修正按钮动作,调用共享实例的
currentLocation方法
修改后的代码
1. LocationManagerModel(修正上下文获取问题,保留原逻辑)
注意:ObservableObject中不能直接使用@Environment(\.managedObjectContext),需通过初始化传入:
import SwiftUI import CoreLocation class LocationManagerModel : NSObject, ObservableObject { enum LocationMode { case ball, shot, putt } private let viewContext: NSManagedObjectContext @Published var shotCoord : CLLocation? @Published var ballCoord : CLLocation? @Published var waiting = false @Published var errorMessage = "" @Published var showError = false @Published var distance: Double? private let locationManager = CLLocationManager() private var mode : LocationMode = .ball // 初始化时传入上下文 public init(viewContext: NSManagedObjectContext) { self.viewContext = viewContext super.init() locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation } public func currentLocation(mode: LocationMode) { self.mode = mode locationManager.requestLocation() } } extension LocationManagerModel : CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { errorMessage = error.localizedDescription showError = true } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { switch mode { case .ball: ballCoord = locations.last! print("shot:\(shotCoord?.coordinate as Any)") print("ball:\(ballCoord?.coordinate as Any)") case .shot: shotCoord = locations.last! print("shot: \(shotCoord?.coordinate as Any)") case .putt: ballCoord = locations.last! waiting = false } } func addNewShot(distance: Double, shotClub: Club) { let club = shotClub let distanceYards = lround(distance * 1.09361) club.strokesList.append(distanceYards) club.strokes = club.strokesList.count let sumArray = club.strokesList.reduce(0,+) club.yardsNum = sumArray / club.strokes do { try viewContext.save() } catch { print("error saving shot: \(error)") } } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .authorizedAlways, .authorizedWhenInUse: return case .denied, .restricted: print("location access denied") case .notDetermined: locationManager.requestWhenInUseAuthorization() @unknown default: print("unknown location authorization status") } } }
2. 修改后的ClubDetailView
struct ClubDetailView: View { @ObservedObject var club: Club @Environment(\.managedObjectContext) private var viewContext @ObservedObject var locationManager: LocationManagerModel // 接收共享实例 @State var newShot: Int = 0 @Binding var shotClub: Club var body: some View{ List { if !club.putter { Section { Text(club.name) Text("Average distance: \(club.yardsNum) yards") Text("\(club.strokes) Strokes Counted") } } else { Text("This app is not designed to track putting data at this time.") } } if !locationManager.waiting && !club.putter { Button(action: { locationManager.currentLocation(mode: .shot) locationManager.waiting = true shotClub = club },label: { Text("Swing Location") .foregroundColor(.white) .font(.system(.title, design: .rounded, weight: .bold)) .frame(maxWidth: .infinity) }) .buttonStyle(.borderedProminent) } if locationManager.waiting && !club.putter{ Button(action: { locationManager.currentLocation(mode: .ball) print("shot: \(locationManager.shotCoord?.coordinate as Any)") print(shotClub.name) shotClub = club // 计算距离并保存(如果需要) if let shotLoc = locationManager.shotCoord, let ballLoc = locationManager.ballCoord { let distance = shotLoc.distance(from: ballLoc) locationManager.addNewShot(distance: distance, shotClub: shotClub) } if shotClub.putter { locationManager.waiting = false } }, label: { Text("Ball Location") .foregroundColor(.white) .font(.system(.title, design: .rounded, weight: .bold)) .frame(maxWidth: .infinity) }) .buttonStyle(.borderedProminent) } if locationManager.waiting && club.putter{ Button(action: { shotClub = club locationManager.waiting = false }, label: { Text("Start Putting") .foregroundColor(.white) .font(.system(.title, design: .rounded, weight: .bold)) .frame(maxWidth: .infinity) }) .buttonStyle(.borderedProminent) } } }
3. 修改后的ContentView
import SwiftUI import CoreLocation struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest( entity: Club.entity(), sortDescriptors:[NSSortDescriptor(keyPath: \Club.yardsNum, ascending: false)], animation: .default) private var clubs:FetchedResults<Club> @State private var shotClub = Club() @State private var showNewClub: Bool = false @StateObject private var locationManager: LocationManagerModel // 共享实例 // 初始化时传入上下文创建locationManager init() { let viewContext = PersistenceController.shared.container.viewContext _locationManager = StateObject(wrappedValue: LocationManagerModel(viewContext: viewContext)) } var body: some View { NavigationView{ ZStack { List { ForEach(clubs) {club in NavigationLink( destination: ClubDetailView( club: club, locationManager: locationManager, shotClub: $shotClub ), label:{ HStack { Text(club.name) Spacer() if !club.putter { Text("\(club.yardsNum)y") } } } ) } .onDelete(perform: deleteClub) } if clubs.isEmpty && !showNewClub{ Button("Tap to Start Adding Clubs!") { showNewClub = true } .foregroundColor(.accentColor) .font(.system(.largeTitle,design: .rounded)) } if showNewClub{ BlankView() .onTapGesture {showNewClub = false} .navigationBarHidden(true) NewClubView(isShow: $showNewClub) .transition(.move(edge: .bottom)) .animation(.default, value: showNewClub) .padding(.top) } } .navigationTitle("Clubs") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showNewClub = true }) { Image(systemName: "plus.circle.fill") .foregroundColor(.accentColor) } } ToolbarItem(placement: .navigationBarTrailing) { EditButton() .foregroundColor(.accentColor) .disabled(clubs.isEmpty) } } } } private func deleteClub(index: IndexSet) { withAnimation { index.map { clubs[$0] }.forEach(viewContext.delete) do { try viewContext.save() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError.localizedDescription), \(nsError.userInfo)") } } } }
关键说明
- 所有视图共享同一个
LocationManagerModel实例,shotCoord/ballCoord/waiting的修改会统一同步,返回列表后值不会丢失 - 移除了不必要的
@Binding绑定,直接通过共享实例访问@Published变量,简化数据流 - 修正了
LocationManagerModel中上下文获取的错误,确保Core Data操作正常执行
内容的提问来源于stack exchange,提问作者jhbetts
相关产品推荐
相关产品推荐

