MapKit Map() custom user location annotation - dictionary

I've got a Map() in my code, don't think you need my code as it's literally just a Map() with the region set. But I was wondering how I can customize the showsUserLocation: True . I want it to display an SF Symbol icon.
Let me know if you need anything else.
Thanks.

Try this approach to display a SF Symbol at a desired location, works well for me.
import Foundation
import SwiftUI
import MapKit
import CoreLocation
struct ContentView: View {
var body: some View {
MapView()
}
}
struct MyLocation: Identifiable {
let id = UUID()
var name: String
var coordinate: CLLocationCoordinate2D
}
struct MapView: View {
#State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 35.661991, longitude: 139.762735), span: MKCoordinateSpan(latitudeDelta: 0.0925, longitudeDelta: 0.0925))
// simulated user location
#State var myLocations = [MyLocation(name: "tokyo", coordinate: CLLocationCoordinate2D(latitude: 35.661991, longitude: 139.762735))]
var body: some View {
Map(coordinateRegion: $region, interactionModes: .all,
showsUserLocation: true,
userTrackingMode: .constant(.follow),
annotationItems: myLocations) { myLoc in
MapAnnotation(coordinate: myLoc.coordinate) {
Image(systemName: "figure.wave").resizable() // <-- here
.frame(width: 33, height: 33)
.foregroundColor(.red)
}
}
}
}

Related

swiftUI tabView pagetabviewstyle - button to next is not working

import SwiftUI
GuideImageView
currentPage mean 1VStack which is in text and images from array from guidelists
struct GuideImageView: View {
#State var currentPage: Int = 0
var body: some View {
VStack{
TabView(selection: $currentPage){
ForEach(guidelists){i in
VStack{
Text(i.explain)
Image(i.image)
.resizable()
}
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) //page처럼 구현 + ...을 안보이게함
Button("Next") {
if currentPage == 3 {
currentPage = 0
//return
}else{
currentPage += 1
}
}
}
}
}
Struct GuideList
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id = UUID() //UUID = 고유식별자
let image: String
let explain: String
}
let guidelists
let guidelists = [
GuideList(image: "image1",explain: "explain1." ),
GuideList(image: "image2",explain: "explain2." ),
GuideList(image: "image3",explain: "explain3." )
]
GuideImageView_Previews
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
GuideImageView()
}
}
I want to make button to go to next page
button seems doesn't work
The reason for this not working is the type mismatch in your models id and the selection var.
Detail:
TabView(selection: $currentPage){
ForEach(guidelists){i in
these two lines tell the compiler that the id for every element is of type UUID (because GuideList is identifieable and id is of type UUID. Thats fine for itself, but TabView has a selection var of type Int (currentPage is an Int) so it is not working. So changing one of both types to equal the other will solve the problem.
easy example:
Change your code to:
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id: Int
let image: String
let explain: String
}
let guidelists = [
GuideList(id: 0, image: "image1",explain: "explain1."),
GuideList(id: 1, image: "image2",explain: "explain2." ),
GuideList(id: 2, image: "image3",explain: "explain3." )
]

How do I make a Button in SwiftUi trigger a function in the UIKit wrapper?

I can add MKPointAnnotations by Long Tap Gesture to my Mapkit MapView.
Now I want to remove those markers by pressing a Button in Swift UI.
My idea was to set a variable true when the button is pressed and use this variable as condition for a function in the updateUIView function. But I get the error message that i can't refer to this variable in this nested function.
Here's a snippet from my addAnnotations wrapper. It works fine. How can I implement the removeAnnotation function on my mapView?
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView()
map.setRegion(region, animated: false)
map.showsUserLocation = true
map.delegate = context.coordinator
locationManager.delegate = context.coordinator
let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.addAnnotation(gesture:)))
longPress.minimumPressDuration = 1
map.addGestureRecognizer(longPress)
return map
}
class Coordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
var mapView: MapView
init(mapView: MapView) {
self.mapView = mapView
}
#objc func addAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == .ended {
if let mapView = gesture.view as? MKMapView {
let point = gesture.location(in: mapView)
let locationCoordinate = mapView.convert(point, toCoordinateFrom: mapView)
let myPin = MKPointAnnotation()
myPin.title = "Latitude: \(locationCoordinate.latitude), Longitude: \(locationCoordinate.longitude)"
myPin.coordinate = locationCoordinate
mapView.addAnnotation(myPin)
}
}
}
Firstly, I don't know why you chose to use MapKit with UIKit in a SwiftUI project I assume, when you can do it all in SwiftUI unless you intend to support iOS 13 but anyways here is how you do it:
You have your imports of course:
import SwiftUI
import CoreLocation
import MapKit
Then create a viewModel like so:
class MapViewModel: ObservableObject {
#Published var didPressButton = false
}
Add an #StateObject property wrapper(avoid using #ObservevedObject if you are creating the instance in your ContentView)
And yes you can also use #EnvironmentObject
struct ContentView: View {
#StateObject var viewModel = MapViewModel()
var body: some View {
ZStack {
MapView(viewModel: viewModel)
Button("Perform Action") {
viewModel.didPressButton.toggle()
}
}
}
}
Then create an #ObservableObject which will share the same instance of MapViewModel from your ContentView
struct MapView: UIViewRepresentable {
#ObservableObject var viewModel: MapViewModel
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func updateUIView(_ uiView: MKMapView, context: Context) {
if viewModel.didPressButton == true {
context.coordinator.performActionFromSwiftUI()
}
}
func makeUIView(context: Context) -> MKMapView {
let map = context.coordinator.mapView
map.setRegion(region, animated: false)
map.showsUserLocation = true
map.delegate = context.coordinator
locationManager.delegate = context.coordinator
let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.addAnnotation(gesture:)))
longPress.minimumPressDuration = 1
map.addGestureRecognizer(longPress)
return map
}
}
In your Coordinator class, add the function which will be called in your UIViewRepresentable updateUIView method.
I also suggest when returning a mapView in the makeUIView method in your UIViewRepresentable struct, return the context.coordinator.mapView rather than creating an instance there.
class Coordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
var mapView = MKMapView()
func performActionFromSwiftUI() {
print("tapped")
}
#objc func addAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == .ended {
if let mapView = gesture.view as? MKMapView {
let point = gesture.location(in: mapView)
let locationCoordinate = mapView.convert(point, toCoordinateFrom: mapView)
let myPin = MKPointAnnotation()
myPin.title = "Latitude: \(locationCoordinate.latitude), Longitude: \(locationCoordinate.longitude)"
myPin.coordinate = locationCoordinate
mapView.addAnnotation(myPin)
}
}
}
}

Update the data stored on Firebase by inputting new values to TextFields where there have been default values

I designed a method to show the data stored on Firebase in the textfields according to this link. Here is my solution:
import Foundation
import SwiftUI
struct TextFieldWithDefaultValue: View {
var model: userViewModel // Actual a more complex view model
var textFieldName: String
#State var editedValue: String
init(model: userViewModel, text: String) {
self.model = model
self.textFieldName = text
switch self.textFieldName {
case "Name": self._editedValue = State(wrappedValue: model.user.name)
case "Bio": self._editedValue = State(wrappedValue: model.user.bio)
case "Interest": self._editedValue = State(wrappedValue: model.user.interest)
default: self._editedValue = State(wrappedValue: "No records")
}
}
var body: some View {
TextField(textFieldName, text: $editedValue)
}
}
One of its shortages is that I cannot bind the value of the input of object State. Therefore, the value on Firebase is never changed. So my question is, is there a shortcut that allows me to update the value whenever a user's input is different from the original one? Here is the rest of my code:
#StateObject var currentUser: userViewModel
#State private var showingAlert = false
var body: some View {
TextFieldWithDefaultValue(model: currentUser, text: "Interest")
.padding()
.keyboardType(.numberPad)
.background(Color.white.opacity(0.06))
.cornerRadius(15)
Button(action: {
currentUser.updatePersonalInfo()
self.showingAlert = true
}, label: {
Text("Save")
.foregroundColor(Color("Color"))
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.padding(.vertical)
.frame(width: UIScreen.main.bounds.width - 100)
.clipShape(Capsule())
})
.disabled(currentUser.getName() != "" || currentUser.getInterest() != "" || currentUser.getBio() != "" ? false : true)
.opacity(currentUser.getName() != "" || currentUser.getInterest() != "" || currentUser.getBio() != "" ? 1 : 0.5)
.alert(isPresented: $showingAlert) {
() -> Alert in
Alert(title: Text("Congratulations!"), message: Text("Saved successfully!"), dismissButton: .default(Text("OK")))
}
}
A workaround is to set the StateObject variable, which I pass to the TextField with the actual value in init() function in the View.
#StateObject var currentUser: userViewModel
init(currentUser: userViewModel) {
self._currentUser = StateObject(wrappedValue: currentUser)
}
TextField("Name", text: $currentUser.user.name)
You can use onChange modifier to track changes of the text and propagate it to view model like this
TextField(textFieldName, text: $editedValue)
.onChange(of: editedValue) { newValue in
viewModel.saveToFirebase(newValue)
}
The other thing you could do is make your view model anObservableObject and add editedValue there as a #Published property. Then from the View you do this:
struct TextFieldWithDefaultValue: View {
#ObservedObject var viewModel: UserViewModel
var body: some View {
TextField(textFieldName, text: viewModel.$editedValue)
}
}
and inside the view model you can use Combine to subscribe to value’s changes and save data like this:
class UserViewModel: ObservableObject {
#Published var editedValue = “”
init(…) {
// Init `editedValue` here
$editedValue
.sink { // Save data here }
.store(in: …)
}
}
The only other thing you might want to think about is that you probably don’t want to update value on Firebase every time a single character changes. But this is already out of scope of this question

How do I use TextInput for WatchOS in swiftUI

Typically I would use presentTextInputControllerWithSuggestions() to show the TextInput field. But this isn't available in swiftUI because it is a function of WKInterfaceController. Do I have to use the WKInterfaceController for this?
I couldn't find anything in the documentation.
You can use extension for View in SwiftUI:
extension View {
typealias StringCompletion = (String) -> Void
func presentInputController(withSuggestions suggestions: [String], completion: #escaping StringCompletion) {
WKExtension.shared()
.visibleInterfaceController?
.presentTextInputController(withSuggestions: suggestions,
allowedInputMode: .plain) { result in
guard let result = result as? [String], let firstElement = result.first else {
completion("")
return
}
completion(firstElement)
}
}
}
Example:
struct ContentView: View {
var body: some View {
Button(action: {
presentInputController()
}, label: {
Text("Press this button")
})
}
private func presentInputController() {
presentInputController(withSuggestions: []) { result in
// handle result from input controller
}
}
}
This would be done through a TextField in SwiftUI.

Assign action tap to google map marker, swift 2

I have a google map marker and i want that when i tap the marker send me to another viewController, or display a button on my map,
let marker1 = GMSMarker()
marker1.position = CLLocationCoordinate2DMake(24.8236423, -107.4234671)
marker1.appearAnimation = kGMSMarkerAnimationPop
marker1.icon = UIImage(named: "flag_icon")
marker1.title = "Any title"
marker1.snippet = "Any text"
marker1.map = mapView
I solved it, this is the tap function
//Market Tap Function
func mapView(mapView: GMSMapView!, didTapMarker marker: GMSMarker!) -> Bool {
let myFirstButton = UIButton()
myFirstButton.setTitle("✸", forState: .Normal)
myFirstButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
myFirstButton.frame = CGRectMake(15, -50, 300, 500)
myFirstButton.addTarget(self, action: "pressed:", forControlEvents: .TouchUpInside)
self.view.addSubview(myFirstButton)
return true
}
You can use these steps for swift 4 and 5
implement GMSMapViewDelegate
punch your map to this delegate in didviewload() like this : mygooglemap.delegate = self
add this function to your class:
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
//do what ever you want
return true
}
For Swift 5+
Your Can Do This Using Delegete method
Assign your mapview delete to self
Confrim GMSMapViewDelegateto your self
Here is Code
1
import UIKit
import GoogleMaps
import GooglePlaces
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
}
}
2
extension HomeVC:GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print("Do what ever you want.")
return true
}
}

Resources