I am working on a project that gathers real-time location data and stores it in a database (using google firebase). I am relatively new to Swift but have been coding for about 2 years. I have created a basic HomePage UI and a MapView UI, using a navigation link to move from page to page. The MapViewModel class is a class used to gather location data from the user. I am trying to call a method of the MapViewModel class with .onAppear. Although when I run the program the function is not called and "inside" isn't printed on the terminal. Am I missing something on how .onAppear and navigation links work? Please let me know what the best solution would be to call the function when the view is switched to MapView.
import SwiftUI
import MapKit
struct HomePage: View {
#State private var viewModel = MapViewModel()
var body: some View {
NavigationView {
ZStack {
LinearGradient(
gradient: Gradient(colors: [.blue, Color(red:0.18, green: 0.79, blue: 0.91, opacity: 1.0)]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
NavigationLink {
MapView()
}
label: {
ZStack {
Circle()
.trim(from: 0.5)
.frame(width: 450, height: 450)
.foregroundColor(Color(red: 1.0, green: 0.89, blue: 0.36, opacity: 1.0))
Text("Navigate")
.foregroundColor(.white)
.font(.system(size:32, weight: .bold, design: .default))
.padding(.bottom, 280)
}
.position(x: 195, y: 900)
.ignoresSafeArea(.all)
}
}
}
}
}
struct MapView: View {
#State private var viewModel = MapViewModel()
var body: some View {
Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
.ignoresSafeArea()
.accentColor(Color(.systemPink))
.onAppear {
print("inside")
viewModel.checkIfLocationServiceIsEnabled()
}
}
}
final class MapViewModel : NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 33, longitude: -120), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
var locationManager: CLLocationManager?
func checkIfLocationServiceIsEnabled() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager!.startUpdatingLocation()
locationManager!.delegate = self
}
else {
print("Print error message")
}
}
func checkLocationAuthorization() {
guard let locationManager = locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("Location restricted")
case .denied:
print("You have denied location permission, go to settings")
case .authorizedAlways, .authorizedWhenInUse:
print("Inside")
if locationManager.location?.coordinate != nil {
region = MKCoordinateRegion(center: locationManager.location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
}
else {
print("Location not found")
}
#unknown default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
print(lastLocation)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
HomePage()
}
}
Use #StateObject var viewModel = MapViewModel() in your HomePage view. Add .environmentObject(viewModel) to your NavigationView and the corresponding #EnvironmentObject var viewModel: MapViewModel in MapView. Works for me.
This is the code I used for my tests, that works for me.
import Foundation
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
HomePage()
}
}
struct HomePage: View {
#StateObject var viewModel = MapViewModel() // <-- here
var body: some View {
NavigationView {
ZStack {
LinearGradient(
gradient: Gradient(colors: [.blue, Color(red:0.18, green: 0.79, blue: 0.91, opacity: 1.0)]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
NavigationLink {
MapView()
}
label: {
ZStack {
Circle()
.trim(from: 0.5)
.frame(width: 450, height: 450)
.foregroundColor(Color(red: 1.0, green: 0.89, blue: 0.36, opacity: 1.0))
Text("Navigate")
.foregroundColor(.white)
.font(.system(size:32, weight: .bold, design: .default))
.padding(.bottom, 280)
}
.position(x: 195, y: 900)
.ignoresSafeArea(.all)
}
}
}.environmentObject(viewModel) // <-- here
}
}
struct MapView: View {
#EnvironmentObject var viewModel: MapViewModel // <-- here
var body: some View {
Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
.ignoresSafeArea()
.accentColor(Color(.systemPink))
.onAppear {
print("\n----> in onAppear \n")
viewModel.checkIfLocationServiceIsEnabled()
}
}
}
final class MapViewModel : NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 33, longitude: -120), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
func checkIfLocationServiceIsEnabled() {
print("\n----> in checkIfLocationServiceIsEnabled")
}
}
If that does not work with your system, try this:
struct MapView: View {
#EnvironmentObject var viewModel: MapViewModel
var body: some View {
VStack { // <-- here
Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
.ignoresSafeArea()
.accentColor(Color(.systemPink))
}
.onAppear {
print("\n----> in onAppear \n")
viewModel.checkIfLocationServiceIsEnabled()
}
}
}
Related
I want to make my slider go back to its original position after I have unlocked it once by dragging to the end.
The animation goes till the end and stays there.
I want it to go back to its initial position once the unlock swipe is complete, meaning once the unlock lock image is shown, it should reset to again being locked.
How can I achieve this?
I am attaching complete code. It has three components, Dragging component and background component that are being called in main Unlock button view.
//
// DraggingComponent.swift
// VirtualDoorman
//
// Created by Engr. Bushra on 11/9/22.
//
import SwiftUI
struct DraggingComponent: View {
#Binding var isLocked: Bool
let isLoading: Bool
let maxWidth: CGFloat
private let minWidth = CGFloat(50)
#State private var width = CGFloat(50)
var duration: Double = 0.3
var body: some View {
RoundedRectangle(cornerRadius: 16)
.fill(Color.red)
.opacity(width / maxWidth)
.frame(width: width)
.overlay(
Button(action: { }) {
ZStack {
image(name: "lock", isShown: isLocked)
progressView(isShown: isLoading)
image(name: "lock.open", isShown: !isLocked && !isLoading)
}
.animation(.easeIn(duration: 0.35).delay(0.55), value: !isLocked && !isLoading)
}
.buttonStyle(BaseButtonStyle())
.disabled(!isLocked || isLoading),
alignment: .trailing
)
.simultaneousGesture (
DragGesture()
.onChanged { value in
guard isLocked else { return }
if value.translation.width > 0 {
width = min(max(value.translation.width + minWidth, minWidth), maxWidth)
}
}
.onEnded { value in
guard isLocked else { return }
if width < maxWidth {
width = minWidth
UINotificationFeedbackGenerator().notificationOccurred(.warning)
} else {
UINotificationFeedbackGenerator().notificationOccurred(.success)
withAnimation(.spring().delay(0.5)) {
isLocked = false
// DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.2) {
// withAnimation(.easeOut(duration: duration)) {
// width = min(max(value.translation.width + minWidth, minWidth), maxWidth)
//
// }
// }
// isLocked = true
}
}
}
)
.animation(.spring(response: 0.5, dampingFraction: 1, blendDuration: 0), value: width)
}
private func image(name: String, isShown: Bool) -> some View {
Image(systemName: name)
.font(.system(size: 20, weight: .regular, design: .rounded))
.foregroundColor(Color("BlueAccent"))
.frame(width: 42, height: 42)
.background(RoundedRectangle(cornerRadius: 14).fill(.white))
.padding(4)
.opacity(isShown ? 1 : 0)
.scaleEffect(isShown ? 1 : 0.01)
}
private func progressView(isShown: Bool) -> some View {
ProgressView()
.progressViewStyle(.circular)
.tint(.white)
.opacity(isShown ? 1 : 0)
.scaleEffect(isShown ? 1 : 0.01)
}
}//struct
//struct DraggingComponent_Previews: PreviewProvider {
// static var previews: some View {
// DraggingComponent(maxWidth: 10)
// }
//}
struct BaseButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.95 : 1)
.opacity(configuration.isPressed ? 0.9 : 1)
.animation(.default, value: configuration.isPressed)
}
}
//
// BackgroundComponent.swift
// VirtualDoorman
//
// Created by Engr. Bushra on 11/9/22.
//
import SwiftUI
struct BackgroundComponent: View {
#State private var hueRotation = false
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 16)
.fill(LinearGradient(
colors: [Color("BlueAccent").opacity(0.8), Color("BlueAccent").opacity(0.8)],
startPoint: .leading,
endPoint: .trailing
)
)
.hueRotation(.degrees(hueRotation ? 20 : -20))
Text("Slide to unlock")
.font(.footnote)
.bold()
.foregroundColor(.white)
.frame(maxWidth: .infinity)
}
.onAppear {
withAnimation(.linear(duration: 3).repeatForever(autoreverses: true)) {
hueRotation.toggle()
}
}
}
}
struct BackgroundComponent_Previews: PreviewProvider {
static var previews: some View {
BackgroundComponent()
}
}
extension Color {
static let pinkBright = Color(red: 247/255, green: 37/255, blue: 133/255)
static let blueBright = Color(red: 67/255, green: 97/255, blue: 238/255)
static let blueDark = Color(red: 58/255, green: 12/255, blue: 163/255)
}
//
// Unlock_Button.swift
// VirtualDoorman
//
// Created by Engr. Bushra on 11/9/22.
//
import SwiftUI
struct Unlock_Button: View {
#State private var isLocked = true
#State private var isLoading = false
var completion: (() -> Void)?
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
BackgroundComponent()
DraggingComponent(isLocked: $isLocked, isLoading: isLoading, maxWidth: geometry.size.width)
}
}
.frame(height: 50)
.padding()
.onChange(of: isLocked) { isLocked in
guard !isLocked else { return }
simulateRequest()
}
}
private func simulateRequest() {
isLoading = true
// completion?()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
isLoading = false
}
}
}
struct Unlock_Button_Previews: PreviewProvider {
static var previews: some View {
Unlock_Button()
}
}
I could not rotate the green part in the circular path.
I want to set dynamically infinite time.
I created a struct for ProgressView and want to set it as ProgressView in my project.
how to implement the red and black colour curve design in the rotation
path
struct CircularProgressView: View {
#State var progressValue: Float = 0.0
var body: some View {
ZStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
}.onAppear{
self.incrementProgress()
}
}
func incrementProgress() {
let randomValue = Float([0.012, 0.022, 0.034, 0.016, 0.11, 0.012].randomElement()!)
self.progressValue += randomValue
}
}
struct ProgressBar: View {
#Binding var progress: Float
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.red)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 2.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.green)
.rotationEffect(Angle(degrees: 270))
.animation(.linear)
}
}
}
This is the Preview struct.
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
Thanks in advance for helping me.
struct CircularProgressView: View {
#State var progressValue: Float = 0.0
#State private var isLoaderVisible: Bool = false
var degree = 90
var body: some View {
ZStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
}.overlay(LoaderView(showLoader: isLoaderVisible))
.onAppear {
isLoaderVisible.toggle()
}
}
}
struct ProgressBar: View {
#Binding var progress: Float
#State var degree:Double = 90
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.foregroundColor(Color.green)
}
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
// enum for LoaderView
public enum LoaderAnimation {
case low, medium, high
var animationSpeed: Double {
switch self {
case .low: return 1.0
case .medium: return 0.8
case .high: return 10.2
}
}
}
This this the LoaderView which we set on overlay and animate
public struct LoaderView: View {
var loaderAnimationSpeed: LoaderAnimation? = .high
var showLoader: Bool = false
public var body: some View {
GeometryReader { reader in
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.yellow)
Circle()
.trim(from: 0.6, to: 1)
.stroke(.green, lineWidth: 20)
.rotationEffect(.degrees(showLoader ? 360 : 0))
.animation(Animation.easeInOut(duration: showLoader ? loaderAnimationSpeed?.animationSpeed ?? 0.0 : 1).repeatForever(autoreverses: false), value: showLoader)
}
}
}
public init( loaderAnimationSpeed: LoaderAnimation? = .medium, showLoader: Bool) {
self.loaderAnimationSpeed = loaderAnimationSpeed
self.showLoader = showLoader
}
}
firstly I am really new to iOS development and Swift (2 weeks coming here from PHP :))
I am trying to build a iOS application that has a side menu. And my intention is when I click on a item in the menu the new view will appear on screen like the 'HomeViewController' and for each consequent item like example1, 2 etc (In place of the menu button, Note I will be adding the top nav bar soon to open the menu)
I am wondering how I can accomplish this feature?
Thanks
ContentView.swift
import SwiftUI
struct MenuItem: Identifiable {
var id = UUID()
let text: String
}
func controllView(clickedview:String) {
print(clickedview)
}
struct MenuContent: View{
let items: [MenuItem] = [
MenuItem(text: "Home"),
MenuItem(text: "Example1"),
MenuItem(text: "Example2"),
MenuItem(text: "Example3")
]
var body: some View {
ZStack {
Color(UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1))
VStack(alignment: .leading, spacing: 0) {
ForEach(items) {items in
HStack {
Text(items.text)
.bold()
.font(.system(size: 20))
.multilineTextAlignment(/*#START_MENU_TOKEN#*/.leading/*#END_MENU_TOKEN#*/)
.foregroundColor(Color.white)
Spacer()
}
.onTapGesture {
controllView(clickedview: items.text)
}
.padding()
Divider()
}
Spacer()
}
.padding(.top, 40)
}
}
}
struct SideMenu: View {
let width: CGFloat
let menuOpen: Bool
let toggleMenu: () -> Void
var body: some View {
ZStack {
//Dimmed backgroud
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.15))
.opacity(self.menuOpen ? 1 : 0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.toggleMenu()
}
//Menucontent
HStack {
MenuContent()
.frame(width: width)
.offset(x: menuOpen ? 0 : -width)
.animation(.default)
Spacer()
}
}
}
}
struct ContentView: View {
#State var menuOpen = false
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
if menuOpen {
withAnimation {
print("Left")
menuOpen.toggle()
}
}
}
if $0.translation.width > -100 {
if !menuOpen {
withAnimation {
print("Right")
menuOpen.toggle()
}
}
}
}
ZStack {
if !menuOpen {
Button(action: {
self.menuOpen.toggle()
}, label: {
Text("Open Menu")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.background(Color(.systemBlue))
})
}
SideMenu(width: UIScreen.main.bounds.width/1.6, menuOpen: menuOpen, toggleMenu: toggleMenu)
}
.edgesIgnoringSafeArea(.all)
.gesture(drag)
}
func toggleMenu(){
menuOpen.toggle()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The on tap command from the above code:
.onTapGesture {
controllView(clickedview: items.text)
}
HomeViewController.swift
import UIKit
import WebKit
class HomeViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://developer.apple.com")!
webView.load(URLRequest(url: url))
}
}
You'll need a couple of ingredients:
A way to store the state of the currently active view
A way to communicate the state between your menu and main content view
For the first one, I made an enum that listed the different types of views (ViewType) and added it to your MenuItem model.
For the second, you can pass state via a #Binding from parent to child views and back up the chain.
struct MenuItem: Identifiable {
var id = UUID()
let text: String
let viewType : ViewType
}
enum ViewType {
case home, example1, example2, example3
}
struct MenuContent: View{
#Binding var activeView : ViewType
let items: [MenuItem] = [
MenuItem(text: "Home", viewType: .home),
MenuItem(text: "Example1", viewType: .example1),
MenuItem(text: "Example2", viewType: .example2),
MenuItem(text: "Example3", viewType: .example3)
]
var body: some View {
ZStack {
Color(UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1))
VStack(alignment: .leading, spacing: 0) {
ForEach(items) { item in
HStack {
Text(item.text)
.bold()
.font(.system(size: 20))
.multilineTextAlignment(.leading)
.foregroundColor(Color.white)
Spacer()
}
.onTapGesture {
activeView = item.viewType
}
.padding()
Divider()
}
Spacer()
}
.padding(.top, 40)
}
}
}
struct SideMenu: View {
let width: CGFloat
let menuOpen: Bool
let toggleMenu: () -> Void
#Binding var activeView : ViewType
var body: some View {
ZStack {
//Dimmed backgroud
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.15))
.opacity(self.menuOpen ? 1 : 0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.toggleMenu()
}
//Menucontent
HStack {
MenuContent(activeView: $activeView)
.frame(width: width)
.offset(x: menuOpen ? 0 : -width)
.animation(.default)
Spacer()
}
}
}
}
struct ContentView: View {
#State private var menuOpen = false
#State private var activeView : ViewType = .home
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
if menuOpen {
withAnimation {
print("Left")
menuOpen.toggle()
}
}
}
if $0.translation.width > -100 {
if !menuOpen {
withAnimation {
print("Right")
menuOpen.toggle()
}
}
}
}
ZStack {
VStack {
if !menuOpen {
Button(action: {
self.menuOpen.toggle()
}, label: {
Text("Open Menu")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50, alignment: .center)
.background(Color(.systemBlue))
})
}
switch activeView {
case .home:
HomeViewControllerRepresented()
case .example1:
Text("Example1")
case .example2:
Text("Example2")
case .example3:
Text("Example3")
}
}
SideMenu(width: UIScreen.main.bounds.width/1.6,
menuOpen: menuOpen,
toggleMenu: toggleMenu,
activeView: $activeView)
.edgesIgnoringSafeArea(.all)
}
.gesture(drag)
}
func toggleMenu(){
menuOpen.toggle()
}
}
struct HomeViewControllerRepresented : UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> HomeViewController {
HomeViewController()
}
func updateUIViewController(_ uiViewController: HomeViewController, context: Context) {
}
}
I'm using SwiftUI new Map view to display pins for annotations, and would like the pins, when clicked, to display a view for editing the annotation's name and description.
I've tried to use MapAnnotation with a view that contains a button with a pin image. The image displays correctly, but the button doesn't work.
Is it possible to do this without falling back to a UIViewRepresentable of MKMapView?
import SwiftUI
import MapKit
struct ContentView: View {
#State private var showingEditScreen = false
#State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.334722,
longitude: -122.008889),
span: MKCoordinateSpan(latitudeDelta: 1,
longitudeDelta: 1)
)
#State private var pins: [Pin] = [
Pin(name: "Apple Park",
description: "Apple Inc. headquarters",
coordinate: CLLocationCoordinate2D(latitude: 37.334722,
longitude:-122.008889))
]
var body: some View {
Map(coordinateRegion: $region,
interactionModes: .all,
annotationItems: pins,
annotationContent: { pin in
MapAnnotation(coordinate: pin.coordinate,
content: {
PinButtonView(pin: pin)
})
})
}
}
My Pin definition:
struct Pin: Identifiable {
let id = UUID()
var name: String
var description: String
var coordinate: CLLocationCoordinate2D
}
The view with the button and pin image:
struct PinButtonView: View {
#State private var showingEditScreen = false
#State var pin: Pin
var body: some View {
Button(action: {
showingEditScreen.toggle()
}) {
Image(systemName: "mappin")
.padding()
.foregroundColor(.red)
.font(.title)
}
.sheet(isPresented: $showingEditScreen,
content: {
EditView(pin: self.$pin)
})
}
}
Editing view:
struct EditView: View {
#Environment(\.presentationMode) var presentationMode
#Binding var pin: Pin
var body: some View {
NavigationView {
Form {
TextField("Place name", text: $pin.name)
TextField("Description", text: $pin.description)
}
.navigationTitle("Edit place")
.navigationBarItems(trailing: Button("Done") {
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
As of XCode 12.3 this appears to be functioning with buttons. One key gotcha that I noticed though was that if you use offsets then it's possible your buttons will be placed out of the tappable area of the annotation.
In order to counter that you can add additional padding to account for the offset and keep it within tappable bounds:
MapAnnotation(coordinate: item.placemark.coordinate) {
ZStack {
MapPinView() // My view for showing a precise pin
VStack { // A prompt with interactive option, offset by 60pt
Text("Use this location?")
HStack {
Button("Yes", action: {
print("yes")
})
Button("No", action: {
print("no")
})
}
}
.offset(x: 0, y: 60) // offset prompt so it's below the marker pin
}
.padding(.vertical, 60) // compensate for offset in tappable area of annotation. pad both the top AND the bottom to keep contents centered
}
I have a SwiftUI MapKit view that uses MapAnnotations with a pin that is tappable and it displays a View with information in.
It is not pretty but it works for now iOS 14 only.
https://github.com/PhilStollery/BAB-Club-Search/blob/main/Shared/views/AnnotatedMapView.swift
import MapKit
import SwiftUI
struct AnnotatedMapView: View {
#ObservedObject
private var locationManager = LocationManager()
// Default to center on the UK, zoom to show the whole island
#State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 54.4609,
longitude: -3.0886),
span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10))
#ObservedObject var store: ClubStore
var body: some View {
ZStack {
Map(coordinateRegion: $region,
showsUserLocation: true,
annotationItems: store.clubs!) {
club in MapAnnotation(coordinate: club.coordinate) {
VStack{
if club.show {
ZStack{
RoundedRectangle(cornerRadius: 10)
.fill(Color(UIColor.systemBackground))
.shadow(radius: 2, x: 2, y: 2)
VStack{
NavigationLink(destination: ClubLocationView(club: club)) {
Text(club.clubname)
.fontWeight(.bold)
}
.padding()
Text(club.association)
.padding(.leading)
.padding(.trailing)
Text(club.town)
.padding(.bottom)
}
}
.onTapGesture {
let index: Int = store.clubs!.firstIndex(where: {$0.id == club.id})!
store.clubs![index].show = false
}
} else {
Image(systemName: "house.circle")
.font(.title)
.foregroundColor(.accentColor)
.onTapGesture {
let index: Int = store.clubs!.firstIndex(where: {$0.id == club.id})!
store.clubs![index].show = true
}
}
}
}
}
.edgesIgnoringSafeArea(.bottom)
}
.navigationBarTitle(Text("Map View"), displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
withAnimation {
self.region.center = locationManager.current!.coordinate
self.region.span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
}
})
{Image(systemName: "location")}
)
}
}
I created a circularprogress view to be able to show a progress bar according to the steps data. But for some reason I can not reach to the step.count inside my stepView file.
This is my StepView
struct StepView: View {
private var healthStore: HealthStore?
#State private var presentClipboardView = true
#State private var steps: [Step] = [Step]()
init() {
healthStore = HealthStore()
}
private func updateUIFromStatistics(_ statisticsCollection: HKStatisticsCollection) {
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
statisticsCollection.enumerateStatistics(from: startOfDay, to: now) { (statistics, stop) in
let count = statistics.sumQuantity()?.doubleValue(for: .count())
let step = Step(count: Int(count ?? 0), date: statistics.startDate, wc: Double(count ?? 0 / 1000 ))
steps.append(step)
}
}
var body: some View {
VStack {
ForEach(steps, id: \.id) { step in
VStack {
HStack{
Text("WC")
Text("\(step.wc)")
}
HStack {
Text("\(step.count ?? 0)")
Text("Total Steps")
}
Text(step.date, style: .date)
.opacity(0.5)
CircularProgress(steps: step.count) //ERROR
Spacer()
}
}
.navigationBarBackButtonHidden(true)
}
.onAppear() {
if let healthStore = healthStore {
healthStore.requestAuthorization { (success) in
if success {
healthStore.calculateSteps { (statisticsCollection) in
if let statisticsCollection = statisticsCollection {
updateUIFromStatistics(statisticsCollection)
}
}
}
}
}
}
.onDisappear() {
self.presentClipboardView.toggle()
}
}
}
and this is my circularprogress view
struct CircularProgress: View {
var steps: Binding<Int>
var body: some View {
ZStack {
Color.progressBarColor
.edgesIgnoringSafeArea(.all)
VStack {
ZStack {
Label()
Outline(steps: steps)
}
}
}
}
}
struct Label: View {
var percentage: CGFloat = 20
var body : some View {
ZStack {
Text(String(format: "%.0f", percentage))
.font(Font.custom("SFCompactDisplay-Bold", size: 56))
}
}
}
struct Outline: View {
var steps: Binding<Int>
var percentage: CGFloat = 20
var colors : [Color] = [Color.trackProgressBarColor]
var body: some View {
ZStack {
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle()
.trim(from: 0, to: percentage * 0.01)
.stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
.fill(AngularGradient(gradient: .init(colors: colors), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)))
).animation(.spring(response: 2.0, dampingFraction: 1.0, blendDuration: 1.0))
}
}
}
I am getting this error at stepview WHILE CALLING CIRCULARPROGRESS inside the stepview. I guess I am trying to get the data in the wrong way.
I don't see necessity of binding here, so just replace corresponding places with simple Int:
struct CircularProgress: View {
var steps: Int
and
struct Outline: View {
var steps: Int