You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中@Published变量无法作为@Binding持久传递的问题

问题分析与解决方案

核心问题

你遇到的shotCoord和ballCoord返回后重置为nil的根本原因是:ClubDetailView内部创建了独立的LocationManagerModel实例,按钮点击时调用的是这个局部实例的currentLocation方法,修改的是局部实例的坐标变量,而非ContentView中共享的那个实例的变量。而waiting是通过@Binding直接绑定到ContentView的共享实例,所以能正常工作。

修复步骤

  1. 移除ClubDetailView内的局部LocationManagerModel实例,改为从外部传入共享实例
  2. 取消shotCoord/ballCoord的@Binding绑定,直接通过共享的LocationManagerModel实例访问这些@Published变量
  3. 修正按钮动作,调用共享实例的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 06:49:53