I am trying to pass shotCoord
, ballCoord
, and waiting
from LocationManagerModel
to ContentView
, and then to the DetailView
of items on a list. I need these three variables to maintain their values when returning to the ContentView
and selecting another item from the list. Waiting
is used to control the state of the DetailView
and works perfectly. ballCoord
and shotCoord
do not persist when returning to the `ContentView` and are reset to nil
.
import SwiftUI
import CoreLocation
class LocationManagerModel : NSObject, ObservableObject {
enum LocationMode {
case ball, shot, putt
//addNewShot(distance: Double, shotClub: Club)
}
@Environment(\.managedObjectContext) private var viewContext
//@ObservedObject var club: Club
@Published var shotCoord : CLLocation?
@Published var ballCoord : CLLocation?
@Published var waiting = false
@Published var errorMessage = ""
@Published var showError = false
@Published var distance: Double?
//@Published var shotClub: Club
private let locationManager = CLLocationManager()
private var mode : LocationMode = .ball
public override init() {
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!
//distance = ballCoord?.distance(from: shotCoord)
print("shot:\(shotCoord?.coordinate as Any)")
print("ball:\(ballCoord?.coordinate as Any)")
//print(distance!)
//addNewShot(distance: distance!, shotClub: Club)
case .shot:
shotCoord = locations.last!
print("shot: \(shotCoord?.coordinate as Any)")
case .putt:
ballCoord = locations.last!
waiting = false
}
}
func addNewShot(distance: Double, shotClub: Club) -> Void {
let club = shotClub
let distanceYards = lround(distance * 1.09361)
//let avgYards = club.yardsNum * club.strokes
//club.strokes += 1
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")
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways:
return
//print("authorizedAlways")
case .authorizedWhenInUse:
return
//print("authorizedWhenInUse")
case .denied, .restricted: print("denied")
// show the error to the user
case .notDetermined: print("notDetermined")
locationManager.requestWhenInUseAuthorization()
@unknown default: print("This should never appear")
}
}
}
struct ClubDetailView: View {
@ObservedObject var club: Club
@Environment(\.managedObjectContext) private var viewContext
@StateObject private var locationManager = LocationManagerModel()
@State var newShot: Int = 0
@Binding var waiting: Bool
@Binding var shotCoord: CLLocation?
@Binding var ballCoord: CLLocation?
@Binding var shotClub: Club
var body: some View{
//let waiting = locationManager.waiting
//let shotList = self.club.strokesList
List {
if self.club.putter == false {
Section {
Text(self.club.name)
Text("Average distance: \(club.yardsNum) yards")
Text("\(club.strokes) Strokes Counted")
}
//ForEach(shotList) {shots in club.strokesList}
} else {Text("This app is not designed to track putting data at this time.")}
}
if waiting == false && club.putter == false {
Button(action: {locationManager.currentLocation(mode: .shot)
waiting = true
shotClub = club
},label: {
Text("Swing Location")
.foregroundColor(.white)
.font(.system(.title, design: .rounded, weight: .bold))
.frame(maxWidth: .infinity)
})
.buttonStyle(.borderedProminent)
}
if waiting == true && club.putter == false{
Button(action: {locationManager.currentLocation(mode: .ball)
//ballCoord = getBallLocation()
//let distanceMeters =
// let distanceYards = lround(distanceMeters * 1.09361)
//addNewShot(newShot: Int(distanceYards), shotClub: shotClub)
print("shot: \(shotCoord?.coordinate as Any)")
print(shotClub.name)
shotClub = club
//print(locationManager.shotCoord!)
if shotClub.putter == true {
waiting = false
}
}, label: {
Text("Ball Location")
.foregroundColor(.white)
.font(.system(.title, design: .rounded, weight: .bold))
.frame(maxWidth: .infinity)
})
.buttonStyle(.borderedProminent)
}
if waiting == true && club.putter == true{
Button(action: {
//ballCoord = getBallLocation()
//let distanceMeters = ballCoord.distance(from: shotCoord)
//let distanceYards = lround(distanceMeters * 1.09361)
//addNewShot(newShot: Int(distanceYards), shotClub: shotClub)
//print(shotClub.strokesList)
shotClub = club
if shotClub.putter == true {
waiting = false
}
}, label: {
Text("Start Putting")
.foregroundColor(.white)
.font(.system(.title, design: .rounded, weight: .bold))
.frame(maxWidth: .infinity)
})
.buttonStyle(.borderedProminent)
}
}
}
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 waiting = false
//@State private var shotCoord = CLLocation(latitude: 0.0, longitude: 0.0)
//@State private var ballCoord = CLLocation(latitude: 0.0, longitude: 0.0)
@State private var shotClub = Club()
@State private var showNewClub: Bool = false
@StateObject private var locationManager = LocationManagerModel()
var body: some View {
NavigationView{
ZStack {
List {
ForEach(clubs) {club in
NavigationLink(destination: ClubDetailView(club: club, waiting: $locationManager.waiting,shotCoord: $locationManager.shotCoord, ballCoord: $locationManager.ballCoord, shotClub: $shotClub), label:{HStack {
Text(club.name)
Spacer()
if club.putter == false {
Text("\(club.yardsNum)y")
}
}} )
} //: ForEach
.onDelete(perform: deleteClub)
} //: List
if clubs.count == 0 && self.showNewClub == false{
Button("Tap to Start Adding Clubs!") {
self.showNewClub = true
} .foregroundColor(.accentColor)
.font(.system(.largeTitle,design: .rounded))
}
if self.showNewClub{
BlankView()
.onTapGesture {self.showNewClub = false}
.navigationBarHidden(true)
NewClubView(isShow: self.$showNewClub)
.transition(.move(edge: .bottom))
.animation(.default, value: self.showNewClub)
.opacity(100)
.padding(.top)
}
} //: ZStack
.navigationTitle("Clubs")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
self.showNewClub = true
}) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.accentColor)
} //: Button
} //: ToolbarItem
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
.foregroundColor(.accentColor)
.opacity(100)
.disabled(self.clubs.count == 0)
} //: ToolbarItem
// if waiting == true{
// ToolbarItem(placement: .navigationBarLeading) {
// Button(action: {
//
//
// }, label: {
// Text("Start Putting")
// })
// }
// }
}
}//: NavigationView
//.searchable(text: self.$searchClub)
}
private func deleteClub(index: IndexSet) -> Void {
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)")
}
}
}
}
I have tried passing the variables directly from the LocationManagerModel
to the DetailView
, passing from LocationManagerModel
to ContentView
as @State
then to DetailView
as @Binding
, and from LocationManagerView
to DetailView
as @State
with varying success. What is confusing me the most is that waiting
works as intended by passing it as an argument to the DetailView
when called in the ContentView
but the others do not.
Currently you declare two separate @StateObject private var locationManager = LocationManagerModel()
, one in ClubDetailView
, and one in ContentView
, they have no relations to each other.
You need to have only one source of truth.
So you could use @EnvironmentObject var locationManager: LocationManagerModel
in ClubDetailView
and pass the model to it from ContentView
using .environmentObject(locationManager)
.