Swiftui Button Interact with Map

SwiftUI Button interact with Map

Actually I think in your case, as you have a reference to map, you can try to interact with it directly (aka imperatively, because it is such by nature, so no need to make simple thing complex)

Like

...

let myMapHolder = MapView()
var body: some View {
ZStack {
myMapHolder
.userTrackingMode(.follow)
.edgesIgnoringSafeArea(.all)
...

VStack() {
Button(action: {
self.myMapHolder.mapView.userTrackingMode = _your_mode_
}) {
Image(systemName: "location.fill")

Want to show zoom in zoom out on map through buttons in swiftUI

You have to operate with span parameter of MKCoordinateRegion for that (the less value the more zoomed area), like (demo zoom on 10%):

Button("Zoom In") {
region.span.latitudeDelta *= 0.9
region.span.longitudeDelta *= 0.9
}

Tested with Xcode 13.2 / iOS 15.2

How do I make a clickable pin in SwiftUI's new Map view?

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
}

How to display MKCompassButton with swiftui

Actually compass is showing, but only when you try to turn your map. If you want to see compass button for all the time, you can add your own button in makeUIView func:

struct RootMapView: View {
var body: some View {
MapView()
}
}

struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView()
map.showsCompass = false // hides current compass, which shows only on map turning

let compassBtn = MKCompassButton(mapView: map)
compassBtn.frame.origin = CGPoint(x: 20, y: 20) // you may use GeometryReader to replace it's position
compassBtn.compassVisibility = .visible // compass will always be on map
map.addSubview(compassBtn)

return map
}

func updateUIView(_ uiView: MKMapView, context: Context) {
}
}

Sample Image

SwiftUI - Mapkit - Binding mapkit and show view on annotation callout buttons

import SwiftUI
import MapKit
class Store: Identifiable, ObservableObject {
let id: UUID = UUID()
@Published var title: String?
@Published var latitude: Double
@Published var longitude: Double
@Published var displayStoreImage: Bool
@Published var displayRedMarker: Bool
@Published var redStore: Bool
init(title: String, latitude: Double , longitude: Double, displayStoreImage: Bool, displayRedMarker: Bool, redStore: Bool ) {
self.title = title
self.latitude = latitude
self.longitude = longitude
self.displayRedMarker = displayRedMarker
self.displayStoreImage = displayStoreImage
self.redStore = redStore

}
}
extension Store{
var coordinate: CLLocationCoordinate2D{
get{
return CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude)
}
}
}

struct MapViewParent: View {
@State var selectedListing: Store?
@State var results: [Store] = [Store(title: "Point 1 title", latitude: 38.3 , longitude: 123.2, displayStoreImage: true, displayRedMarker: true, redStore: false ), Store(title: "Point 2 title", latitude: 38.4 , longitude: 123.1, displayStoreImage: true, displayRedMarker: false, redStore: true )
]
var body: some View {
VStack{
Button("clear-storepage", action: {
selectedListing = nil
})
if selectedListing != nil{
TopMapMenu(results: $results, selectedListing: $selectedListing)
}else{
MapViewAnnotations(selectedListing: $selectedListing, results: $results)
}
}
}
}
struct MapViewAnnotations: View {
@State var region: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 38.3 , longitude: 123.2 ), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))

@Binding var selectedListing: Store?
@Binding var results: [Store]

var body: some View {
Map(coordinateRegion: $region, annotationItems: results, annotationContent: {
listing in
MapAnnotation(coordinate: listing.coordinate, content: {
Button(action: {
self.selectedListing = listing as Store?
self.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: listing.coordinate.latitude , longitude: listing.coordinate.longitude ), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
}, label: {
if listing.displayStoreImage{
if listing.displayRedMarker{
Image(systemName: "mappin.circle.fill").foregroundColor(.red).contentShape(Circle())
}else{
Image(systemName: "mappin").foregroundColor(.green).contentShape(Circle())
}
}
else{
Image(systemName: "mappin.circle.fill").foregroundColor(.red).contentShape(Circle())
}
}).buttonStyle(PlainButtonStyle())
})
})

}
}

struct TopMapMenu: View {
@Binding var results: [Store]
@Binding var selectedListing: Store?
var body: some View {
List{
ForEach(results, id: \.id){store in
StoreView(store: store, selectedListing: $selectedListing, results: $results)
}
}
}
}
struct StoreView: View {
@ObservedObject var store: Store
@Binding var selectedListing: Store?
@Binding var results: [Store]
var body: some View{
HStack{
VStack(alignment: .leading, spacing: 10){
Button(action: {
store.displayRedMarker.toggle()
store.redStore = store.displayRedMarker
}, label: {
HStack {
if store.redStore {
Image(systemName: "heart.fill")
}
else { Image(systemName: "heart") }

Text("Restaurant")
}
Spacer()
.background(Color(.white))
}
)}
MapViewAnnotations(selectedListing: $selectedListing, results: Binding(get: {
return [store]
}, set: {
let idx = results.firstIndex(where: {
store.id == $0.id
})
if idx != nil && $0.first != nil{
results[idx!] = $0.first!
}
}))
}
}
}
struct MapViewParent_Previews: PreviewProvider {
static var previews: some View {
MapViewParent()
}
}

SwiftUI: Interaction between Views

Nevermind, i solved it by myself after @vadian's comment (thank you mate).

ContentView:

import SwiftUI
import MapKit

struct ContentView: View
{
@State private var expandedInfoView: Bool = false
@State private var buttonDecision: Int = 0

var body: some View
{
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom))
{
GeometryReader
{
proxy in
VStack(alignment: .center)
{
MapView(mapCenter: $buttonDecision)
VStack
{
HStack
{
Spacer()
MapViewControls(mapCenter: $buttonDecision)
.padding(.top, -700)
}
}
LogoView()
.onTapGesture
{
withAnimation
{
expandedInfoView.toggle()
}
}
.padding(.top, -150)
}
.sheet(isPresented: $expandedInfoView)
{
InfoView()
}
}
}
}
}

struct ContentView_Previews: PreviewProvider
{
static var previews: some View
{
ContentView()
}
}

MapView:

import SwiftUI
import MapKit
import CoreLocation

struct MapView: UIViewRepresentable
{
@Binding var mapCenter: Int
let fszCoordinates = CLLocation(latitude: XXX, longitude: YYY)
var locationManager = CLLocationManager()

func setupManager()
{
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}

func makeUIView(context: Context) -> MKMapView
{
setupManager()

let fszAnnotation = MKPointAnnotation()
fszAnnotation.coordinate = CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude)
fszAnnotation.title = "TITLE"
fszAnnotation.subtitle = "SUBTITLE"

let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.userTrackingMode = .follow
mapView.showsScale = true
mapView.showsCompass = true
mapView.setCenter(CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude), animated: true)
mapView.showAnnotations([fszAnnotation], animated: false)

let buttonLocateUser = MKUserTrackingButton(mapView: mapView)
buttonLocateUser.layer.backgroundColor = UIColor(white: 1, alpha: 0.8).cgColor
buttonLocateUser.layer.borderColor = UIColor.white.cgColor
buttonLocateUser.layer.borderWidth = 1
buttonLocateUser.layer.cornerRadius = 5
buttonLocateUser.translatesAutoresizingMaskIntoConstraints = false
mapView.addSubview(buttonLocateUser)

let scale = MKScaleView(mapView: mapView)
scale.legendAlignment = .trailing
scale.translatesAutoresizingMaskIntoConstraints = false
mapView.addSubview(scale)

NSLayoutConstraint.activate([buttonLocateUser.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25),
buttonLocateUser.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -10),
scale.trailingAnchor.constraint(equalTo: buttonLocateUser.leadingAnchor, constant: -10),
scale.centerYAnchor.constraint(equalTo: buttonLocateUser.centerYAnchor)])

return mapView
}

func updateUIView(_ uiView: MKMapView, context: Context)
{
if ($mapCenter.wrappedValue == 0)
{
uiView.setCenter(CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude), animated: true)
}
else if ($mapCenter.wrappedValue == 1)
{
uiView.setCenter(CLLocationCoordinate2D(latitude: uiView.userLocation.coordinate.latitude, longitude: uiView.userLocation.coordinate.longitude), animated: true)
}
}
}

struct MapView_Previews: PreviewProvider
{
@State static var fszCoordinates = 0
static var previews: some View
{
MapView(mapCenter: $fszCoordinates)
}
}

MapViewControls:

import SwiftUI

struct MapViewControls: View
{
@Binding var mapCenter: Int

var body: some View
{
VStack(spacing: 6)
{
VStack(spacing: 12)
{
Button
{
buttonActionZoomToFSZIT()
}
label:
{
Image(systemName: "house.circle")
}
Divider()
Button
{
buttonActionZoomToUser()
}
label:
{
Image(systemName: "location.circle")
}
}
.frame(width: 40)
.padding(.vertical, 12)
.background(Color(UIColor.secondarySystemGroupedBackground))
.cornerRadius(8)
}
.font(.system(size: 20))
.foregroundColor(.blue)
.padding()
.shadow(color: Color(UIColor.black.withAlphaComponent(0.1)), radius: 4)
}

func buttonActionZoomToFSZIT()
{
self.mapCenter = 0
}

func buttonActionZoomToUser()
{
self.mapCenter = 1
}
}

struct MapViewControls_Previews: PreviewProvider
{
@State static var fszCoordinates = 0

static var previews: some View
{
MapViewControls(mapCenter: $fszCoordinates)
}
}

SwiftUI Views blocking touches to MapKit but not other views

Yes, even transparent images do not allow hit-through so far. In your case, simple enough, the possible approach is to create custom shape, like below. Shapes do pass hits.

Here is simple demo of shape to show the direction.

demo

struct Cross: Shape {
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: CGPoint(x: rect.midX, y: 0))
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
path.move(to: CGPoint(x: 0, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
path.move(to: CGPoint(x: rect.midX, y: rect.midY))
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: 10, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: false)
}
}
}

struct ContentView: View {
var body: some View {
ZStack {
MapView()
Cross().stroke(Color.red)
.frame(width: 90, height: 90)
}
}
}

Dismiss a map view (MKMapView) in SwiftUI

I assume you meant this...

struct DemoView: View {
@State private var showMap = true

var body: some View {
ZStack {
if showMap {
MapView(centerCoordinate:
Binding(get: {CLLocationCoordinate2D(
latitude: self.refSpot.userLatitude,
longitude: self.refSpot.userLongitude)},
set: {newValue in}))
.edgesIgnoringSafeArea(.all)
}

Button(action: {
self.showMap.toggle()
}) {
VStack {
HStack {
Image(uiImage: UIImage(named: "Back.png")!)
.font(.title)
.foregroundColor(.gray)
Spacer()
}
Spacer()
}
}
}
}
}

Map and Scrollview in SwiftUI

ScrollView takes all available space and doesn't lend size constraints to child views. Because Map acts similarly, with no size constraints from the parent, within a ScrollView, it can just collapse into nothing (eg a frame of 0,0).

To fix this, you can add an explicit frame modifier to the map to tell it what size to be:

Map(...) { ... }.frame(width: 300, height: 300)


Related Topics



Leave a reply



Submit