How to integrate Mapbox SDK with SwiftUI - swift

I installed the Mapbox SDK into my project, but I don't understand how to integrate this code snippet with SwiftUI.
I created a SwiftUI View named MapView, where I import the Mapbox Framework.
I try to use the UIViewRepresentable protocol, as in Apple's tutorial, but without success.
import Mapbox
class MapView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let styleURL = URL(string: "mapbox://styles/mapbox/outdoors-v9")
let mapView = MGLMapView(frame: view.bounds,
styleURL: styleURL)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(CLLocationCoordinate2D(latitude: 45.52954,
longitude: -122.72317),
zoomLevel: 14,
animated: false)
view.addSubview(mapView)
}
}
I am new to iOS development, so any help would be much appreciated.

This is a working sample on how you can integrate the MGLMapView with SwiftUI.
When using UIViewRepresentable you have to implement two delegates: makeUIView that will return your view (in this case MGLMapView) and updateUIView which will receive the same view type as the one returned in makeUIView (again MGLMapView).
You can use this to experiment.
Also I recommend you get familiar with the React architecture to understand better the SwiftUI approach.
You can improve this sample by making ContentView receive a styleURL, and trying different styles in the preview.
import Mapbox
import SwiftUI
struct ContentView : View {
var body: some View {
MapboxViewRepresentation(MGLStyle.streetsStyleURL)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
struct MapboxViewRepresentation : UIViewRepresentable {
let styleURL: URL
init(_ styleURL: URL) {
self.styleURL = styleURL
}
func makeUIView(context: UIViewRepresentableContext<MapboxViewRepresentation>) -> MGLMapView {
let mapView = MGLMapView(frame: .zero, styleURL: styleURL)
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapboxViewRepresentation>) {
}
}
UPDATE: Here is the official guide on how to integrate Mapbox with SwiftUI https://docs.mapbox.com/help/tutorials/ios-swiftui/

Related

How do I inject CSS/JS in a WKWebView using SwiftUI?

I'm new to SwiftUI and trying to inject some custom CSS/JS into a page loaded with WKWebView:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string: "https://example.com")!)
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ })
webView.load(request)
webView.configuration.userContentController.addUserScript(WKUserScript( source: "alert('debug')", injectionTime: .atDocumentEnd, forMainFrameOnly: true))
}
}
Which is load like this:
struct ContentView: View {
var body: some View {
WebView()
}
}
Sadly, the code doesn't seem to actually inject anything. I've tried running it before webView.load as well. Having been googling quite a bit, I only see examples done in UIKit and unfortunately, I'm too inexperienced to wrap UIKit in a way that I can use with SwiftUI.
Any guidance would be greatly appreciated.
First of all try to avoid including business code in your views whenever you can. You may use two functions in the Webkit API if you want to include/inject JS to the webview content: EvaluateJS and AddUserScript. You may use "AddUserScript" before the "load" starts. Also please not that "alert" function in JS, would not work in current Mobile Safari. You should have see the text colors to appear in blue with the script below.
Result:
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
VStack {
CustomWebview()
}
.padding()
}
}
struct SwiftUIWebView: UIViewRepresentable {
typealias UIViewType = WKWebView
let webView: WKWebView
func makeUIView(context: Context) -> WKWebView {
webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
final class SwiftUIWebViewModel: ObservableObject {
#Published var addressStr = "https://www.stackoverflow.com"
let webView: WKWebView
init() {
webView = WKWebView(frame: .zero)
loadUrl()
}
func loadUrl() {
guard let url = URL(string: addressStr) else {
return
}
webView.configuration.userContentController.addUserScript(WKUserScript( source: """
window.userscr ="hey this is prior injection";
""", injectionTime: .atDocumentStart, forMainFrameOnly: false))
webView.load(URLRequest(url: url))
// You will have the chance in 8 seconds to open Safari debugger if needed. PS: Also put a breakpoint to injectJS function.
DispatchQueue.main.asyncAfter(deadline: .now() + 8.0) {
self.injectJS()
}
}
func injectJS () {
webView.evaluateJavaScript("""
window.temp = "hey here!";
document.getElementById("content").style.color = "blue";
""")
}
}
struct CustomWebview: View {
#StateObject private var model = SwiftUIWebViewModel()
var body: some View {
VStack {
SwiftUIWebView(webView: model.webView)
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How to disable zoom/pinch in webView of ios14?

I am currently doing a project to convert website to ios application and successfully created it. The only problem is that the application can be zoom in and out. One more thing, whenever any form is click, the form will be zoomed. I am new to xcode (1 week of knowledge) pls guide me. Thanks!
Below is my current code.
WebView.swift
import SwiftUI
import WebKit
import UIKit
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
}
struct SwiftUIWebView: UIViewRepresentable {
let url: URL?
func makeUIView(context: Context) -> WKWebView {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences = prefs
return WKWebView(
frame: .zero,
configuration: config
)
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let myURL = url else {
return
}
let request = URLRequest (url: myURL)
uiView.load(request)
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
SwiftUIWebView(url: URL(string: "THE URL"))
.navigationTitle("NAVIGATION NAME")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How to get mapViewDidFailLoadingMap(_:withError) called when mapType is set to satelliteFlyover?

I'm using a MapView and want to display a static image when the map could not be loaded because the device is offline (or tiles could not be loaded for any other reason).
Actually this works when using the MKMapViewDelegate and implementing mapViewDidFailLoadingMap(_:withError).
In my case the MKMapType is .satelliteFlyover and this somehow prevents the delegate method to be called. It works with .satellite for example.
I've tested this both in Simulator and on device by turning off any internet connection. An error is printed to the console
[ResourceLoading] Failed to load key: 34.21.6 t:4 kt:0 type: 4, -1009:
NSURLErrorDomain Error Domain=NSURLErrorDomain Code=-1009 "The
Internet connection appears to be offline."
UserInfo={NSLocalizedDescription=The Internet connection appears to be
offline. But nothing else happens.
Am I doing something wrong? If not: how can I achieve my goal to detect the error correctly? (I'd prefer not to make an URL request or use connectivity detection)
Here is my sample implementation using SwiftUI and a MKMapView wrapped as UIViewRepresentable. Just turn off any internet connection before you run it.
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
RepresentableMapView()
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct RepresentableMapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.mapType = .satelliteFlyover
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: RepresentableMapView
init(_ parent: RepresentableMapView) {
self.parent = parent
}
func mapViewDidFinishRenderingMap(_ mapView: MKMapView, fullyRendered: Bool) {
print("mapViewDidFinishRenderingMap")
}
func mapViewDidFailLoadingMap(_ mapView: MKMapView, withError error: Error) {
print(error)
}
}
}

Issue when integrating Mapbox default Search UI into SwiftUI

Recently I tried to integrate Mapbox default Search UI into my project. My project basically is based on SwiftUI, and the usage example on the Mapbox website is using UIKit. I was attempted to wrapping UIKit into the SwiftUI code. Here is what I wrote on the SearchController which conformed to protocol 'UIViewControllerRepresentable':
struct Search: UIViewControllerRepresentable, LocationProvider {
let userCoordinate = CLLocationCoordinate2D(latitude: 37.785834, longitude: -122.406417)
/// `LocationProvider` protocol implementation
func currentLocation() -> CLLocationCoordinate2D? { userCoordinate }
func makeUIViewController(context: UIViewControllerRepresentableContext<Search>) -> MapboxPanelController {
let searchController = MapboxSearchController(locationProvider: self)
// default panel
let panelController = MapboxPanelController(rootViewController: searchController)
searchController.delegate = context.coordinator
return panelController
}
func updateUIViewController(_ uiViewController: MapboxPanelController, context: UIViewControllerRepresentableContext<Search>) {
}
...
}
And the parent content View I wrote:
...
var body: some View {
VStack{
ZStack {
NavigationMap()
Search(annotations: $annotations, position: $position)
}
}
}
...
The NavigationMap() is the MapView I create on another file. Basically what I tried to do here is to add a default panelController by Mapbox as the child view of the NavigationMap. But the result I ran is I am not able to interact with the NavigationMap anymore. It seems like the searchController completely covers the NavigationMap even with the panelController which allows you to drag the panel up and down. I can't figure out what I did wrong. Here is the original document provided by Mapbox:
class SimpleUISearchViewController: UIViewController, LocationProvider {
lazy var searchController = MapboxSearchController(locationProvider: self)
/// `LocationProvider` protocol implementation
func currentLocation() -> CLLocationCoordinate2D? { mapboxSFOfficeCoordinate }
lazy var panelController = MapboxPanelController(rootViewController: searchController)
let mapView = MGLMapView()
let mapboxSFOfficeCoordinate = CLLocationCoordinate2D(latitude: 37.7911551, longitude: -122.3966103)
override func viewDidLoad() {
super.viewDidLoad()
/// Add MGLMapView on the screen
mapView.frame = view.bounds
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.showsUserLocation = true
mapView.setCenter(mapboxSFOfficeCoordinate, zoomLevel: 15, animated: false)
view.addSubview(mapView)
searchController.delegate = self
addChild(panelController)
}
...
}
I am not sure why I implemented the wrong way in SwiftUI or the default document it provided cannot completely support SwiftUI. May anyone help me with that? I appreciate it.

SwiftUI and Unity

I’m trying to integrate a Unity view in SwiftUI, I have the below code, but when I run the app I get no output, I know SpriteKit and SceneKit are possible and my unity view runs in a standard swift app, I’m wondering if swiftUI is possible.
struct ContentView: View {
var body: some View {
UnityUIView()
}
}
struct UnityUIView : UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate.startUnity()
return UnityGetGLView()!
}
func updateUIView(_ view: UIView, context: Context) {
}
}
I've tried to create a UIViewControllerRepresentable but get the same thing, The screen flashes once and then disappears, I think it's the splash screen as I changed the colour for debugging, no dice.
struct ContentView: View {
var body: some View {
TestUnityViewController()
}
}
struct TestUnityViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startUnity()
let unityView = UnityGetGLView()!
vc.view.backgroundColor = .red
vc.view!.addSubview(unityView)
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
}
}
If I add a delay to the UIViewControllerRepresentable, it works....interesting
struct TestUnityViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = .red
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startUnity()
let unityView = UnityGetGLView()!
vc.view!.addSubview(unityView)
}
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
}
}
For people still having the issue. I haven't investigated yet, but if you are using the new Unity example to integrate Unity as a framework, the delay indeed fixed the issue with SwiftUI.
You can create a SwiftUI View to which the Unity view will be added:
import SwiftUI
struct TestUnityViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
UnityBridge.showUnity()
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let unityView = UnityBridge.getAppController().rootView!
vc.view!.addSubview(unityView)
}
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View {
TestUnityViewController()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here, UnityBridge is a wrapper around the Unity framework instanciation, similar to the Objective C version from the repository.
The methods:
showUnity() creates the UnityFramework instance, and then calls the showUnityWindow method
getAppController calls the appController method from he UnityFramework object
In the meantime I find a better solution, this will do. I imagine there is a better way to do that, maybe an event triggered to know when Unity's view is fully ready.
EDIT:
I created an example repository to show how to integrate Unity in a SwiftUI project: https://github.com/DavidPeicho/unity-swiftui-example