SwiftUI - contextMenu on LazyVGrid cell causing animation crash when Map present - animation

I have a LazyVGrid and a NavigationBarItem button that changes the number of columns. It cycles through 1, 2, 3 then back to 1, etc. I use the .animation(.default) modifier to animate this change. When there is just one column I use a different cell structure that includes a small Map element. I have a contextMenu that works fine for 2 and 3 columns but crashes instantly when used on the single-column cell.
AnimationError[2652:855376] [Unknown process name] CGImageCreate: invalid image alphaInfo: kCGImageAlphaNone. It should be kCGImageAlphaNoneSkipLast
This crash occurs only on my iPhone 11 Pro real device, the simulators handle it fine. If I remove the animation, all is good.
A compilable, stripped-down version:
import SwiftUI
import CoreLocation
import MapKit
struct HillMO: Identifiable {
let id = UUID()
let name: String
let latitude: Double
let longitude: Double
}
struct ContentView: View {
#State private var gridLayout: [GridItem] = [GridItem(), GridItem(), GridItem()]
var body: some View {
NavigationView {
VStack {
GeometryReader { geometry in
ScrollView {
LazyVGrid(columns: gridLayout, alignment: .center, spacing: 8) {
ForEach(hills) { hill in
Cell(hill: hill, width: geometry.size.width, columns: gridLayout.count)
.contextMenu {
Button(action: { }) { Label("Bingo", image: "eyeglasses") }
Button(action: { }) { Label("Bungo", image: "hourglass") }
}
}
}.padding(5)
.animation(.default)
}
}
}
.navigationBarTitle("Bagger")
.navigationBarItems(
trailing: HStack {
Button(action: {
gridLayout = Array(repeating: .init(.flexible()), count: gridLayout.count % 3 + 1)
}) {
Image(systemName: "circle.grid.3x3")
}
}
)
}
}
let hills = [ HillMO(name: "un", latitude: 0.0, longitude: 0.0),
HillMO(name: "dau", latitude: 1.0, longitude: 1.0),
HillMO(name: "tri", latitude: 2.0, longitude: 2.0),
HillMO(name: "pedwar", latitude: 3.0, longitude: 3.0),
HillMO(name: "pump", latitude: 4.0, longitude: 4.0),
HillMO(name: "chwech", latitude: 5.0, longitude: 5.0)
]
}
My Cell and CellMap Views:
struct Cell: View {
var hill: HillMO
var width: CGFloat
var columns: Int
var body: some View {
Group {
if columns != 1 {
ZStack {
Rectangle()
Text(hill.name).foregroundColor(.white)
}
} else {
ZStack {
Rectangle()
HStack {
Text(hill.name).foregroundColor(.white)
Spacer()
CellMap(coordinateRegion: MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: hill.latitude, longitude: hill.longitude),
latitudinalMeters: 300000,
longitudinalMeters: 300000))
.frame(width: (width / CGFloat(columns)) * 0.24, height: (width / CGFloat(columns)) * 0.24)
.clipShape(Circle())
}
}
}
}
.frame(height: (width / CGFloat(columns)) * (columns == 1 ? 0.25 : 1.25))
}
}
struct CellMap: View {
#State var coordinateRegion: MKCoordinateRegion
var body: some View {
Map(coordinateRegion: $coordinateRegion)
}
}

Try to limit your animation only to exact, your, state value
}.padding(5)
.animation(.default, value: gridLayout)
and this will require to make HillMO equatable
struct HillMO: Identifiable, Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id
}
let id = UUID()
let name: String
let latitude: Double
let longitude: Double
}

Related

How to optimize SwiftUI Map drag performance when many annotaitons showing under macOS?

This is really a curious problem for SwiftUI Map under macOS.
The code is definitely the same both on macOS & iOS/iPadOS, if we add tens of annotations on the map, then drag around, it's very smooth on the iOS/iPadOS, but feeling stuck, stuck and stuck on the macOS.
Is there any special coding thing we forget to setting on macOS for SwiftUI map? It's really confusion for the same code but different performance consequences...
Here is the full project files you may download from OneDrive:
https://1drv.ms/u/s!Ank9gPMc1GKYk_UlZ2V3BC_BArMofQ?e=Nsco9k
Below is the code you can check to:
import SwiftUI
import MapKit
struct ContentView: View {
#State var annotationTrackList = [TyphoonMKAnnotation]()
#State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 31.7, longitude: 118.4),
latitudinalMeters: 100_000,
longitudinalMeters: 100_000)
var body: some View {
ZStack {
Map(coordinateRegion: $region, annotationItems: annotationTrackList) { item in
MapAnnotation(coordinate: item.coordinate) {
ZStack(alignment: .center) {
//color
Image(systemName: "bubble.middle.bottom.fill")
.resizable()
.frame(width: 32, height: 32)
.foregroundColor(item.color)
.aspectRatio(contentMode: .fit)
//icon
Image(systemName: "tornado")
.resizable()
.frame(width: 16, height: 16)
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
}
//location title
Text(item.title ?? "")
.font(Font.system(size: 11, weight: .semibold))
.foregroundColor(.white)
}
}
}
.ignoresSafeArea()
.onAppear {
initialValues()
}
}
private func initialValues() {
var annotationList = [TyphoonMKAnnotation]()
let jsonData = TyphoonJsonData()
//forecast
let forecastJson = jsonData.forecast()
guard
let forecastList = forecastJson["forecast"].array
else { return }
for item in forecastList {
let type = item["type"].stringValue
let latitude = item["lat"].stringValue
let longitude = item["lon"].stringValue
let coordinate = CLLocationCoordinate2D(latitude: Double(latitude)!, longitude: Double(longitude)!)
let annotation = TyphoonMKAnnotation(coordinate: coordinate)
annotation.title = type
annotation.color = .pink
annotationList.append(annotation)
}
//history
let trackJson = jsonData.track()
guard
let trackList = trackJson["track"].array
else { return }
for item in trackList {
let type = item["type"].stringValue
let latitude = item["lat"].stringValue
let longitude = item["lon"].stringValue
let coordinate = CLLocationCoordinate2D(latitude: Double(latitude)!, longitude: Double(longitude)!)
let annotation = TyphoonMKAnnotation(coordinate: coordinate)
annotation.title = type
annotation.color = .green
annotationList.append(annotation)
}
self.annotationTrackList = annotationList
}
}
class TyphoonMKAnnotation: Identifiable {
let id = UUID()
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var index: Int?
var color: Color?
init(coordinate: CLLocationCoordinate2D) {
self.coordinate = coordinate
}
}

Tracking scroll position in a List SwiftUI

So in the past I have been tracking the scroll position using a scroll view but I've fallen into a situation where I need to track the position using a List. I am using a List because I want some of the built in real estate to create my views such as the default List styles.
I can get the value using PreferenceKeys, but the issue is when I scroll to far upwards, the PreferenceKey value will default back to its position 0, breaking my show shy header view logic.
This is the TrackableListView code
struct TrackableListView<Content: View>: View {
let offsetChanged: (CGPoint) -> Void
let content: Content
init(offsetChanged: #escaping (CGPoint) -> Void = { _ in }, #ViewBuilder content: () -> Content) {
self.offsetChanged = offsetChanged
self.content = content()
}
var body: some View {
List {
GeometryReader { geometry in
Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("ListView")).origin)
}
.frame(width: 0, height: 0)
content
.offset(y: -10)
}
.coordinateSpace(name: "ListView")
.onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: offsetChanged)
}
}
private struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGPoint = .zero
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
}
And this is my ContentView:
struct ContentView: View {
#State private var contentOffset = CGFloat(0)
#State private var offsetPositionValue: CGFloat = 0
#State private var isShyHeaderVisible = false
var body: some View {
NavigationView {
ZStack {
TrackableListView { offset in
withAnimation {
contentOffset = offset.y
}
} content: {
Text("\(contentOffset)")
}
.overlay(
ZStack {
HStack {
Text("Total points")
.foregroundColor(.white)
.lineLimit(1)
Spacer()
Text("20,000 pts")
.foregroundColor(.white)
.padding(.leading, 50)
}
.padding(.horizontal)
.padding(.vertical, 8)
.frame(width: UIScreen.main.bounds.width)
.background(Color.green)
.offset(y: contentOffset < 50 ? 0 : -5)
.opacity(contentOffset < 50 ? 1 : 0)
.transition(.move(edge: .top))
}
.frame(maxHeight: .infinity, alignment: .top)
)
}
.navigationTitle("Hello")
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitleDisplayMode(.inline)
.frame(maxHeight: .infinity, alignment: .top)
.background(AccountBackground())
}
}
}
The issue was that Other Views in my hierarchy (probably introduced by SwiftUI) could be sending the default value, which is why you start getting zero sometimes.
What I needed was a way to determine when to use or forward the new value, versus when to ignore it.
To do this I had to make my value optional GCPoint, with a nil default value, then in your reduce method when using PreferenceKeys you have to do:
if let nextValue = nextValue() {
value = nextValue
}
Then make sure your CGPoint is an optional value.

SwiftUI Animation - Issue with Tabview

I am having an issue with animations in a tabview. I have a tabview with 2 views.
The first view has a shape with an animation. The second view is a simple text.
When I launch the application, View1 appears and the animation is correct. When I swipe to View2 and come back to View1, the animation no longer appear as intended and is somewhat random. Anyone might know what the issue might be ? Thank you.
ContentView
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
View1()
View2()
} //: TAB
.tabViewStyle(PageTabViewStyle())
.padding(.vertical, 20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
View1
import SwiftUI
struct FollowEffect: GeometryEffect {
var pct: CGFloat = 0
let path: Path
var rotate = true
var animatableData: CGFloat {
get { return pct }
set { pct = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
if !rotate {
let pt = percentPoint(pct)
return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y))
} else {
// Calculate rotation angle, by calculating an imaginary line between two points
// in the path: the current position (1) and a point very close behind in the path (2).
let pt1 = percentPoint(pct)
let pt2 = percentPoint(pct - 0.01)
let a = pt2.x - pt1.x
let b = pt2.y - pt1.y
let angle = a < 0 ? atan(Double(b / a)) : atan(Double(b / a)) - Double.pi
let transform = CGAffineTransform(translationX: pt1.x, y: pt1.y).rotated(by: CGFloat(angle))
return ProjectionTransform(transform)
}
}
func percentPoint(_ percent: CGFloat) -> CGPoint {
let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)
let f = pct > 0.999 ? CGFloat(1-0.001) : pct
let t = pct > 0.999 ? CGFloat(1) : pct + 0.001
let tp = path.trimmedPath(from: f, to: t)
return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY)
}
}
struct Solar2Grid: Shape {
func path(in rect: CGRect) -> Path {
return Solar2Grid.createArcPath(in: rect)
}
static func createArcPath(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height - 20))
path.addArc(center: CGPoint(x: rect.width - 20, y: rect.height - 20), radius: CGFloat(20), startAngle: .degrees(0), endAngle: .degrees(90), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: rect.height))
return path
}
}
struct AnimRecView: View {
#State var flag: Bool = false
var body: some View {
ZStack {
Solar2Grid()
.stroke(Color.purple, style: StrokeStyle( lineWidth: 2, dash: [3]))
Circle()
.foregroundColor(Color.red)
.blur(radius: 3.0)
.frame(width: 8, height: 8).offset(x: -40, y: -40)
.modifier(FollowEffect(pct: self.flag ? 1 :0, path: Solar2Grid.createArcPath(in: CGRect(x: 0, y: 0, width: 80, height: 80)), rotate: false))
.onAppear {
withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) {
self.flag.toggle()
}
}
}
}
}
struct View1: View {
#State var flag: Bool = false
var body: some View {
VStack() {
Text("View1")
Spacer()
HStack() {
AnimRecView()
}
.frame(width: 80, height: 80, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
Spacer()
}
.frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .top, endPoint: .bottom))
.cornerRadius(20)
.padding(.horizontal, 20)
}
}
struct View1_Previews: PreviewProvider {
static var previews: some View {
View1()
}
}
View2
import SwiftUI
struct View2: View {
var body: some View {
Text("View2")
}
}
struct View2_Previews: PreviewProvider {
static var previews: some View {
View2()
}
}
The problem is that .onAppear() is only called once, so the next time the view is shown, the animation doesn't know what to do. The fix is to put an explicit animation on the Circle() itself. Then, when the view comes back on screen, it has the appropriate animation. Like this:
struct AnimRecView: View {
#State var flag: Bool = false
var body: some View {
ZStack {
Solar2Grid()
.stroke(Color.purple, style: StrokeStyle( lineWidth: 2, dash: [3]))
Circle()
.foregroundColor(Color.red)
.blur(radius: 3.0)
.frame(width: 8, height: 8).offset(x: -40, y: -40)
.modifier(FollowEffect(pct: self.flag ? 1 : 0, path: Solar2Grid.createArcPath(in: CGRect(x: 0, y: 0, width: 80, height: 80)), rotate: false))
// Put the explicit animation here
.animation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false), value: flag)
.onAppear {
self.flag = true
}
}
}
}

swiftui freeze when scroll too fast in scrollview with lazyvgrid

I have a widget list in scrollview, each row have 3 elements. Some elements will show a stopwatch by SwiftUI Text timer. The Text timer will increase by 1 every second. Every thing work well at first, but If I scroll the list too fast, the Text timer will be freeze, won't update anymore, unless scroll the list again.
Bellow recording is my screen recording at iPhone 12 Pro Max. For the first time, slowly scroll from the "Category 2" position to the top, and the stopwatch refreshes normally. For the second and third times, quickly scroll to the top, and the stopwatch stops and never refreshes.
Here is my code:
import SwiftUI
extension Date {
func formatDate(_ format: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(from: self)
}
func getZeroData() -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
var startDateStr = self.formatDate("yyyy-MM-dd")
startDateStr = "\(startDateStr) 00:00:00"
return dateFormatter.date(from: startDateStr) ?? Date()
}
}
struct SectionHeaderView: View {
let title: String
var body: some View {
HStack() {
Text(title)
.font(.system(size: 19.0))
.fontWeight(.bold)
Spacer()
}
.padding(.leading, 10.0)
.frame(height: 44.0)
}
}
struct CellView: View {
let date: Date
let categoryIndex: Int
let rowIndex: Int
init(date: Date, categoryIndex: Int, rowIndex: Int) {
self.date = date
self.categoryIndex = categoryIndex
self.rowIndex = rowIndex
//print("CellView init is \(self.categoryIndex)->\(self.rowIndex)")
}
var body: some View {
print("CellView show is \(self.categoryIndex)->\(self.rowIndex)")
// 1. cellview has a very complex layout, so I need to use GeometryReader to get size for inner complex component.
// 2. In order to show the problem conveniently, only the test code is shown here, but the problem in the video above can also be reproduced
return GeometryReader { geo in
VStack() {
Text("\(categoryIndex) -> \(rowIndex)")
if((self.categoryIndex==0 || self.categoryIndex==2) && self.rowIndex<=4) {
Text(date.getZeroData(), style: .timer)
Text(date.formatDate("HH:mm:ss"))
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.gray.opacity(0.2))
}
}
}
struct ContentView: View {
let date: Date = Date()
let widgetGap: CGFloat = 10.0
let widgetWidth: CGFloat
let widgetHeight: CGFloat
let columns: [GridItem]
//let columns: [GridItem] = Array(repeating: .init(.fixed(UIScreen.main.bounds.width/3.0), spacing: 0), count: 3)
init() {
let col: Int = 3
widgetWidth = (UIScreen.main.bounds.width - widgetGap * CGFloat(col + 1))/CGFloat(col)
//let widgetSize = CGSize(width: 160, height: 160)
widgetHeight = widgetWidth
columns = Array(repeating: .init(.fixed(widgetWidth), spacing: widgetGap), count: col)
}
var body: some View {
print("ContentView show")
return ScrollView() {
ForEach(0 ..< 6, id: \.self) { categoryIndex in
Section(header: SectionHeaderView(title: "Category \(categoryIndex)") ){
LazyVGrid(columns: columns, spacing: widgetGap + 5.0) {
ForEach(0 ..< 13, id: \.self) { rowIndex in
// 1. cellview has a very complex layout, so I need to use compositingGroup before cornerRadius to get a high accuracy radius
CellView(date: date, categoryIndex: categoryIndex, rowIndex: rowIndex)
.frame(width: widgetWidth, height: widgetHeight, alignment: .center)
.compositingGroup()
.cornerRadius(10)
.shadow(color: .black.opacity(0.05), radius: 6)
}
}
}
}
}
}
}
I also try to use List to replace Scrollview + Lazyvgrid. This situation has improved. But List is hard to custom style, such as remove seperateline , remove padding.
Is there any way to solve this problem?
SOS!!

How do I set a pin on Current Location, use Pin as Geofence, alert user onEnter, then reset all / start over again?

I'm a product designer trying to spin up a location-based reminder concept to validate a pretty early stage idea. I could prototype all of these screens in Figma, but real-time Location and a contextual alert are foundational to my hypothesis, so here I am pulling my hair out trying to accomplish this in SwiftUI 😅
https://www.figma.com/proto/jxYwPbqe4oqsXBbf6CZDDi/Location-Reminder-Spec?page-id=0%3A1&node-id=1%3A189&viewport=161%2C384%2C0.12701110541820526&scaling=scale-down
I've been piecing this together from a variety of tutorials (Ray Wenderlich, Hacking Swift, Apple's Swift UI) and snippets about Location etc. but most of the tutorials have more objects and views and other stuff that I don't need which is complicating my scrappy MVP collage approach.
So far, I've got an MKMapView centered on my the coordinates of the User's Location and I've got my button displayed.
I'm wondering
How to create a CLCircularRegion using the coordinates from user location?
How to place a MKMapAnnotation on the Map using the coordinates from the user location and how to remove the annotation after pressing a button inside the notification?
With the above, I think I'll be able to sort out how to fire the notification and show the notification onEnter.
I've got 4 files in the project so far...
LocationManager.swift
import Foundation
import MapKit
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
#Published var region = MKCoordinateRegion()
private let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locations.last.map{
region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude),
span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
)
}
}
}
extension CLLocation {
var latitude: Double {
return self.coordinate.latitude
}
var longitude: Double {
return self.coordinate.longitude
}
}
ContentView.swift
import CoreLocation
import MapKit
import SwiftUI
struct ContentView: View {
#StateObject var manager = LocationManager()
#State private var locations = [MKPointAnnotation] ()
var body: some View {
VStack {
HStack {
Text("Location Reminder")
.font(.largeTitle)
.bold()
.foregroundColor(.black)
Spacer()
}
.padding()
ZStack (alignment: .bottomTrailing) {
Map(coordinateRegion: $manager.region,
showsUserLocation: true)
.ignoresSafeArea()
ReminderButton()
.padding()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ReminderButton.swift
import Foundation
import SwiftUI
struct ReminderButton: View {
var body: some View {
VStack {
Spacer()
Button(action: {
}) {
HStack {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 40, height: 40)
Text("Add a Reminder")
}
.padding()
.background(Color.white)
.cornerRadius(40)
.shadow(color: .gray, radius: 0.2, x: 1, y: 1)
}
}
}
}
I have an empty file that I've named Geofence.swift where I plan on creating a CLCircularRegion and setting up the notifications / alerts onEnter etc.
Appreciate any and all help / advice / suggestions. Also please LMK if there are ways to improve my post and/or if it belongs elsewhere.
import SwiftUI
import MapKit
import CoreLocation
struct SingleLocationView: View {
#StateObject var manager = LocationManager()
#State private var locations = [PinnedLocation] ()
#State var userTrackingMode: MapUserTrackingMode = .follow
let radius: CGFloat = 160 //meters approx 0.1 miles
var body: some View {
VStack {
HStack {
VStack{
Text("Location Reminder")
.font(.largeTitle)
.bold()
.foregroundColor(.black)
}
Spacer()
}
.padding()
ZStack (alignment: .bottomTrailing) {
GeometryReader{ geo in
Map(coordinateRegion: $manager.region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $userTrackingMode, annotationItems: locations, annotationContent: { annotation in
MapAnnotation(coordinate: annotation.coordinate, anchorPoint: CGPoint(x: 0.5, y: 0.5), content: {
//Use this vs Circle because your map is a rectangle circumference is an oval
//Maybe a stretched Circle could be a little more accurate
RoundedRectangle(cornerRadius: 25)
.foregroundColor(.white)
.overlay(
Image(systemName: "mappin").foregroundColor(.red))
// The screen vertical and horizontal multiplied by your radius divided Map vertial/horizontal span in meters
.frame(width: (geo.size.width/manager.regionSpanInLongitudeDeltaMeters.value) * radius, height: (geo.size.height/manager.regionSpanInLatitudeDeltaMeters.value) * radius, alignment: .center)
})
})
.ignoresSafeArea()
}
ReminderButton(locations: $locations).environmentObject(manager)
.padding()
}
}
}
}
struct SingleLocationView_Previews: PreviewProvider {
static var previews: some View {
SingleLocationView()
}
}
//You MKAnnotaion is a Protocol you need to create your own class
//MKPointAnnotation is a MKShape for use with MKMapView
class PinnedLocation: NSObject, MKAnnotation, Identifiable{
var title: String?
var subtitle: String?
var coordinate: CLLocationCoordinate2D
init(title: String? = nil, subtitle: String? = nil, coordinate: CLLocationCoordinate2D) {
self.title = title
self.subtitle = subtitle
self.coordinate = coordinate
}
///Get the CLCircularRegion with the given radius
func getCLCircularRegion(radius: CGFloat) -> CLCircularRegion{
CLCircularRegion(center: self.coordinate, radius: radius, identifier: "\(self.id)")
}
}
extension UnitLength{
//This is approx do research on degrees to meters
static let degree = UnitLength(symbol: "degree", converter: UnitConverterLinear(coefficient: 111111))
}
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
#Published var region = MKCoordinateRegion()
//If you do it the way you were doing it get the most current location from here to append to your array
#Published var lastLocation: CLLocation = CLLocation()
///Region gives span in degrees. This is a rough conversion
var regionSpanInLatitudeDeltaMeters: Measurement<UnitLength>{
Measurement(value: region.span.latitudeDelta, unit: UnitLength.degree).converted(to: .meters)
}
///Region gives span in degrees. This is a rough conversion
var regionSpanInLongitudeDeltaMeters: Measurement<UnitLength>{
Measurement(value: region.span.longitudeDelta, unit: UnitLength.degree).converted(to: .meters)
}
private let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
//Don't need it SwiftUI does this for you
//manager.requestWhenInUseAuthorization()
//manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locations.last.map{
region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude),
span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
)
}
lastLocation = locations.last!
}
}
extension CLLocationCoordinate2D{
var annotation: PinnedLocation{
PinnedLocation(coordinate: CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude))
}
}
struct ReminderButton: View {
//The button joins your location manager with the locations displayed. It needs access to both
#EnvironmentObject var manager: LocationManager
#Binding var locations: [PinnedLocation]
var body: some View {
VStack {
Spacer()
Button(action: {
//Append the current center/location to your list of annotions
locations.append(manager.region.center.annotation)
}) {
HStack {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 40, height: 40)
Text("Add a Reminder")
}
.padding()
.background(Color.white)
.cornerRadius(40)
.shadow(color: .gray, radius: 0.2, x: 1, y: 1)
}
}
}
}

Resources