Map view problems - ios

struct SchoolsMap: View {
#EnvironmentObject private var newMap : SchoolModel
var share = SchoolModel().schools
#State private var latitudinal = 0.0
#State private var longitude = 0.0
let pin = MKPointAnnotation()
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D( latitude: 40.503851973654783, longitude: -74.450847996722231), span: MKCoordinateSpan (latitudeDelta: 0.9, longitudeDelta: 0.9))
var body: some View {
NavigationView{
ZStack{
Map(coordinateRegion: $region, annotationItems: share){ location in
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: 40.503851973654, longitude: -74.45084799672)) {
VStack{
NavigationLink{
SchoolsDetailView(data: location)
} label: {
Text(location.properties.name!)
.bold()
Circle()
.stroke(.red)
.frame(width: 44, height: 44)
}
}
}
}
}
.ignoresSafeArea()
}
}
}
My map view doesn't show up with the annotations. The region updates whenever I change it, but the map doesn't show any of my annotations. Anyone know why?

Related

.onAppear() function not working with NavigationLink

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()
}
}
}

How to make SwiftUI MapPin clickable [duplicate]

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")}
)
}
}

SwiftUI: How to switch only first item of an array

I'm working on iOS App with a card-swipe function like Tinder. I have an array with cards. I want to integrate the possibility to flip the card to get more information.
struct TryView: View {
#ObservedObject var cards = APIObs()
#State var back = false
var body: some View {
GeometryReader{geo in
ZStack{
ForEach(cards.shows){ i in
ZStack{
if back == false{
SwipeDetailsView1(name: i.name, age: i.status, image: "https://image.tmdb.org/t/p/w500\(i.image)", height: geo.size.height-80, info: $back) .rotation3DEffect(.degrees(self.back ? 180.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.back ? 0 : 1)
}else{
BackView(name: i.name, height: geo.size.height-80, info: $back) .rotation3DEffect(.degrees(self.back ? 0.0 : 180.0), axis: (x: 0.0, y: -1.0, z: 0.0))
.zIndex(self.back ? 1 : 0)
}
}.animation(.easeOut(duration: 0.25))
}
}
}
}
My Problem is, that when back == true all cards of the array flip. I just want the first one to flip. Does anyone know how to manage that?
That is because your #State var back is for the array.
If you want to control each card you need a CardView with an #State var back for each card.
I put some sample code below. A minimum reproducible example is preferred so we don't have to recreate struct's and other components
import SwiftUI
struct TryView: View {
//#ObservedObject var cards = APIObs()//Code not Provided
#State var cards = ["Alpha", "Beta", "Charlie"]
var body: some View {
GeometryReader{geo in
VStack{
ForEach(cards, id: \.self){ i in
CardView(card: i)
}
}
}
}
}
struct SwipeDetailsView1: View {
var name: String
var body: some View {
Text(name)
}
}
struct BackView: View {
var name: String
var body: some View {
Text(name)
}
}
struct CardView: View {
#State var back = false //Variable for just the card
var card: String
var body: some View {
VStack{
ZStack{
if back == false{
SwipeDetailsView1(name: "front \(card)")
}else{
BackView(name: "back \(card)")
}
}.animation(.easeOut(duration: 0.25))
Button("flip", action: {
back.toggle()
})
}
}
}
struct TryView_Previews: PreviewProvider {
static var previews: some View {
TryView()
}
}

Programmatically drawn circles are not appearing SwiftUI

I am trying to create a test application using SwiftUI where the user can draw on the screen when they drag on the screen. However, I am having some difficulties getting the Circles that I am using to represent the pen to appear.
Here is the ContentView.swift code that I am using.
import SwiftUI
var list_of_points = [CGPoint]()
struct ContentView: View {
var body: some View {
ZStack{
Rectangle().fill(Color.gray)
Text("Hello!")
}.gesture(DragGesture().onChanged({
value in
drag_responder(point: value.location)
}))
}
}
func drag_responder(point: CGPoint){
print("Drawing at \(point)")
list_of_points.append(point)
let pen = Circle().size(CGSize(width:10, height:10)).position(point)
pen
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct Drawing {
var points: [CGPoint] = [CGPoint]()
}
struct ContentView: View {
#State private var currentDrawing: Drawing = Drawing()
#State private var drawings: [Drawing] = [Drawing]()
#State private var color: Color = Color.black
#State private var lineWidth: CGFloat = 3.0
var body: some View {
VStack(alignment: .center) {
DrawingPad(currentDrawing: $currentDrawing,
drawings: $drawings,
color: $color,
lineWidth: $lineWidth)
}
}
}
struct DrawingPad: View {
#Binding var currentDrawing: Drawing
#Binding var drawings: [Drawing]
#Binding var color: Color
#Binding var lineWidth: CGFloat
var body: some View {
GeometryReader { geometry in
Path { path in
for drawing in self.drawings {
self.add(drawing: drawing, toPath: &path)
}
self.add(drawing: self.currentDrawing, toPath: &path)
}
.stroke(self.color, lineWidth: self.lineWidth)
.background(Color(white: 0.95))
.gesture(
DragGesture(minimumDistance: 0.1)
.onChanged({ (value) in
let currentPoint = value.location
if currentPoint.y >= 0
&& currentPoint.y < geometry.size.height {
self.currentDrawing.points.append(currentPoint)
}
})
.onEnded({ (value) in
self.drawings.append(self.currentDrawing)
self.currentDrawing = Drawing()
})
)
}
.frame(maxHeight: .infinity)
}
private func add(drawing: Drawing, toPath path: inout Path) {
let points = drawing.points
if points.count > 1 {
for i in 0..<points.count-1 {
let current = points[i]
let next = points[i+1]
path.move(to: current)
path.addLine(to: next)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How to make SwiftUI View expand available space?

I am trying to make a SwiftUI View expand its available space. I have a MapView which uses the MapKit to display a map on the screen. I would like this map to expand the available space in the VStack. You can see it is above a view colored red, and below a search bar. If i make the red colored view have a height of 100, then the MapView shrinks down. If I do not set the height on the red colored view, then the MapView is bigger however the red view does not look as I want.
I want the red view to have a height of 100, and the MapView to fill all available height underneath the search bar, and above the red view.
ContentView:
struct ContentView: View {
#ObservedObject
var viewModel: HomeViewModel
var body: some View {
NavigationView {
ZStack {
ColorTheme.brandBlue.value.edgesIgnoringSafeArea(.all)
VStack {
CardView {
EditText(hint: "Search", text: self.$viewModel.searchText, textContentType: UITextContentType.organizationName)
}
MapView(annotations: self.$viewModel.restaurantAnnotations)
.cornerRadius(8)
CardView(height: 100) {
HStack {
Color.red
}
}
}
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
CardView
struct CardView<Content>: View where Content : View {
var height: CGFloat = .infinity
var content: () -> Content
var body: some View {
content()
.padding(EdgeInsets.init(top: 0, leading: 8, bottom: 8, trailing: 8))
.background(Color.white.cornerRadius(8))
.shadow(radius: 2, x: 0, y: 1)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: height)
}
}
EditText
struct EditText: View {
var hint: String
#Binding
var text: String
var label: String = ""
var textContentType: UITextContentType? = .none
var keyboardType: UIKeyboardType = .default
var textSize: CGFloat = 16
var body: some View {
return VStack(alignment: .leading) {
Text(label).font(.system(size: 12)).bold()
.foregroundColor(ColorTheme.text.value)
HStack {
TextField(hint, text: $text)
.lineLimit(1)
.font(.system(size: textSize))
.textContentType(textContentType)
.keyboardType(keyboardType)
.foregroundColor(ColorTheme.text.value)
}
Divider().background(ColorTheme.brandBlue.value)
}
}
}
MapView
struct MapView: UIViewRepresentable {
#Binding
var annotations: [MKAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.delegate = context.coordinator
view.addAnnotations(annotations)
if annotations.count == 1 {
let coords = annotations.first!.coordinate
let region = MKCoordinateRegion(center: coords, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
view.setRegion(region, animated: true)
}
}
func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(self)
}
}
MapViewCoordinator
class MapViewCoordinator: NSObject, MKMapViewDelegate {
var mapViewController: MapView
init(_ control: MapView) {
self.mapViewController = control
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
//Custom View for Annotation
let identifier = "Placemark"
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.canShowCallout = true
let button = UIButton(type: .infoDark)
annotationView.rightCalloutAccessoryView = button
return annotationView
}
return nil
}
}
As you can see the MapView does not fill the available space
Now all the available space is filled but the red view height is not 100
Here is a solution (tested with Xcode 11.4):
struct CardView<Content>: View where Content : View {
var height: CGFloat? = nil // << here !!
Note: defined height, even with .infinity, made your upper card equivalent by requesting height to map view, so they divided free space; when height is not specified, the component tights to content

Resources