I've been on and off Swift for the years so, sorry if I'm doing something stupid, the Swift UI thing isn't helping my confusion either. I have the code I'm working with at the bottom.
So I'm making a Geofencing app using SwiftUI, I've got the basics up and running but still have a bit to go with it,
I have the Geofencing coordinates and stuff inside the UserData environment variable, which I get off a json elsewhere.
Have a SwiftUI object that has a map out with the current position and initializes a CLLocationManager to deal with geofencing.
I'm trying to implement the delegate on the same file by
locationManager.delegate = self
in the setupManager(),
but it will cause
Cannot assign value of type 'GeofencingView' to type 'CLLocationManagerDelegate?'
The lack of SwiftUI specific information online seems to be causing the most confusion for me at the moment, I think what I should do is,
create a customized NsObject that handles the delegates, but in that case I'm not sure how to pass the #EnviromentObject
Find out how to put the delegates on the UIViewRepresentable object.
Any pointers on what I'm doing wrong would be greatly appreciated.
import SwiftUI
import MapKit
import CoreLocation
struct GeofencingView: UIViewRepresentable {
#EnvironmentObject var userData: UserData
var notification = LocalNotificationManager()
var locationManager = CLLocationManager()
func setupManager() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
self.startGoefenceMonitoring()
}
func startGoefenceMonitoring() {
print("MapListView::startGeofenceMonitoring")
for landmark in self.userData.landmarks {
let moniteringCordinate = CLLocationCoordinate2DMake(landmark.locationCoordinate.longitude, landmark.locationCoordinate.latitude)
let moniteringRegion = CLCircularRegion.init(center: moniteringCordinate, radius: 20.0, identifier: "\(landmark.id)" )
locationManager.startMonitoring(for: moniteringRegion)
}
}
func makeUIView(context: Context) -> MKMapView {
setupManager()
let mapView = MKMapView(frame: UIScreen.main.bounds)
let count = self.userData.landmarks.count
var annotationArray: [MKAnnotation] = []
var num:Int = 0
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
}
struct GeofencingView_Preview: PreviewProvider {
static var previews: some View {
GeofencingView()
.environmentObject(UserData())
}
}
Related
I want to detect a user tap on the map so i can add a marker and get the coordinates of that point. I was able to do that on android and with react but it seems impossible in swiftui because i can't find new ways to do that.
Currently i have my mapview like this.
import SwiftUI
import UIKit
import MapboxMaps
struct MapBoxMapView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MapViewController {
return MapViewController()
}
func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
}
}
class MapViewController: UIViewController {
internal var mapView: MapView!
override func viewDidLoad() {
super.viewDidLoad()
let myResourceOptions = ResourceOptions(accessToken: "MY_TOKEN")
let myCameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoom: 10)
let initOptions = MapInitOptions(
resourceOptions: myResourceOptions,
cameraOptions: myCameraOptions,
styleURI: StyleURI(rawValue: StyleURI.satellite.rawValue)
)
mapView = MapView(frame: view.bounds, mapInitOptions: initOptions)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapView)
}
}
What can i do to make it possible? I searched on v10 documentation but found nothing.
Overview: I'm using SwiftUI, but wanted to use UIKit-MapKit. I used UIViewRepresentable to be able to wrap the UIKit feature.
Problem: I'm learning about swiftui-uikit-interoperability and I'm getting stuck on being able to display multiple SwiftUI views.
Code Snippet:
ContentView
struct ContentView: View {
#ObservedObject var viewModel: MapView.PinViewModel
init() {
self.viewModel = MapView.PinViewModel()
}
var body: some View {
NavigationView {
MapView()
.sheet(isPresented: $viewModel.showPinForm) {
PinForm()
}
.navigationTitle("SwiftUI UIKit Interop").scaledToFill()
}
}
}
MapView
struct MapView: UIViewRepresentable {
class PinViewModel: ObservableObject {
#Published var showPinForm: Bool
init() {
self.showPinForm = false
}
func updateShowPinVar() {
self.showPinForm = true
}
}
func showPinForm() {
pinViewModel.updateShowPinVar()
}
func makeCoordinator() -> MapViewCoordinator {
let coordinator = MapViewCoordinator()
coordinator.delegate = self
return coordinator
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
let coordinate = CLLocationCoordinate2D(latitude: 40.7209, longitude: -74.0007)
let span = MKCoordinateSpan(latitudeDelta: 0.03, longitudeDelta: 0.03)
let mapRegion = MKCoordinateRegion(center: coordinate, span: span)
mapView.setRegion(mapRegion, animated: true)
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
}
In this I have a #Published var showPinForm that gets toggled in MapView. ContentView is supposed to watch this variable and when it is true it will cause the sheet to pull up. However, I believe when I enter MapView() from ContentView() then I no longer recognize ContentView.
Using the UIViewRepresentable, what is the best way to display another swiftui view? Does not have to use .sheet (Although, it would be nice)
I have tried to simplify the code to show the main problem, so I left out a lot of additional info and took out basic patterns that I used (MVVM)
Please let me know if you need any clarifications
try to follow this pattern, you can toggle the flag both inside and outside your MapView
struct MapView: UIViewRepresentable {
#Binding var switcher: Bool // -> use binding
func makeUIView(context: Context) -> MKMapView { MKMapView() }
func updateUIView(_ uiView: MKMapView, context: Context) { }
}
struct MainView: View {
#ObservedObject var viewModel = MainViewModel()
var body: some View {
MapView(switcher: $viewModel.flag)
.sheet(isPresented: $viewModel.flag) {
Text("Pin pin")
}
}
}
class MainViewModel: ObservableObject {
#Published var flag: Bool = false
}
How can one prevent auto-zooming with IOS MapKit when using userTrackingMode = .followWithHeading? That is I am setting the current user location to the centre of the screen, and have "view.userTrackingMode = .followWithHeading" so that the map orientates to north, but when you zoom in/out manually the MapView automatically overrides this and zooms back to the level it seems to prefer being at.
I was to be able to zoom in, then the zoom level stays like this, whilst it keeps the map centred to user location, and keeps auto-rotating to keep map aligned to north.
I am using SwiftUI so have effectively do have the location being passed into GCMapView as a parameter (as the means to keep the SwiftUI GCMapView up to date with latest user location). So not sure if this is causing an issue?
Some key bits (have pulled some code out to show relevant lines) of the MapKit call backs I'm using:
struct FlightView: View {
#EnvironmentObject var locationManager : GCLocationManager
#State var centreUserLocation : Bool = false
var body: some View {
GCMapView(
flight: flight,
userLocation: locationManager.userLocation,
centreUserLocation: centreUserLocation,
initalZoomDone: initalZoomDone
)
}
}
struct GCMapView : UIViewRepresentable {
let map = MKMapView()
func makeUIView(context: Context) -> MKMapView {
map.delegate = context.coordinator
map.isRotateEnabled = true
map.userTrackingMode = .followWithHeading
map.showsUserLocation = true
return map
}
func updateUIView(_ view: MKMapView, context: Context) {
if let userLocation = userLocation {
view.centerCoordinate = userLocation
view.userTrackingMode = .followWithHeading // Needed to keep map rotating to align to North
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: GCMapView
init(_ parent: GCMapView) {
self.parent = parent
super.init()
}
}
}
I'm trying to implement a camera preview view in SwiftUI, for which I have the following code:
import SwiftUI
import AVFoundation
struct CameraPreview: UIViewRepresentable {
let session: AVCaptureSession
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .gray
let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
videoPreviewLayer.frame = view.bounds
videoPreviewLayer.videoGravity = .resizeAspectFill
videoPreviewLayer.connection?.videoOrientation = .portrait
view.layer.addSublayer(videoPreviewLayer)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
for layer in uiView.layer.sublayers ?? [] {
layer.frame = uiView.bounds
}
}
}
However I do see the gray background view that I set on the view, but it never starts showing the camera output. I've set a AVCaptureVideoDataOutputSampleBufferDelegate class and I can see the frames being captured and processed, yet for some reason it does not start rendering the output.
I have this other snippet that DOES render the output, but it does so by setting the preview layer as the root layer which is what I want to avoid, here's the code that works:
struct CameraPreview: UIViewRepresentable {
let session: AVCaptureSession
func makeUIView(context: Context) -> UIView {
let view = VideoView()
view.backgroundColor = .gray
view.previewLayer.session = session
view.previewLayer.videoGravity = .resizeAspectFill
view.previewLayer.connection?.videoOrientation = .portrait
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
for layer in uiView.layer.sublayers ?? [] {
layer.frame = uiView.bounds
}
}
class VideoView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var previewLayer: AVCaptureVideoPreviewLayer {
layer as! AVCaptureVideoPreviewLayer
}
}
}
Some examples I found showed I should be able to show the preview like I do in the first example. I've tried initializing the session with inputs before and after the preview view is created and I've gotten the same result. Am I missing anything? am I not retaining the layer or is there a special configuration for the session to look out for? to make it work I simply swap the implementations and the one with the inner class does render?
Any help is really appreciated.
Some resources:
https://nsscreencast.com/episodes/296-camera-capture-preview-layer-sample-buffer
https://www.appcoda.com/avfoundation-swift-guide/
https://developer.apple.com/documentation/vision/recognizing_objects_in_live_capture
I'm attempting to use HEREMaps with SwiftUI via UIViewRepresentable and I'm getting the following crash when instantiating NMAMapView. Using HEREMaps version 3.13.3 pod.
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
import SwiftUI
import NMAKit
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> NMAMapView {
let mapView = NMAMapView()
return mapView
}
func updateUIView(_ uiView: NMAMapView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
MapView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Also, no luck with adding NSLocationWhenInUseUsageDescription to my Inof.plist
We have written down a sample code to initialise the NMAPView. Try with the following
import UIKit
import NMAKit
class ViewController: UIViewController {
#IBOutlet weak var mapView: NMAMapView!
private var gestureMarker: NMAMapMarker?
override func viewDidLoad() {
super.viewDidLoad()
//set current controller to be delegate of map view's gesture
mapView.gestureDelegate = self
//add image icon to show current positon, which can shows map view motion when gestures were applied
guard let image = UIImage(named: "indicator") else { return }
let indicatorMarker = NMAMapMarker(geoCoordinates: mapView.geoCenter, image: image)
mapView.add(mapObject: indicatorMarker)
}
This is HERE SDK issue. Unfortunately no workarounds found yet.
Fix will be included in next HERE SDK Release(3.15), which is planned on April 1, 2020.
Thanks for reporting!