I am currently using Google Maps API in SwiftUI, and am trying to get a sheet to appear after a marker infoWindow is tapped programmatically.
In other parts of my app I’m displaying sheets like this, and it’s the same thing I’m trying to achieve here but programmatically:
https://blog.kaltoun.cz/swiftui-presenting-modal-sheet/
Right now I have a function that prints a message when an infoWindow is tapped, but don’t know how to make a SwiftUI view appear within a sheet using the function.
-
Since I’m using SwiftUI, the way I implement the Google Maps API is a little different than in plain Swift.
Here are the basics of my GMView.swift file that handles all the google maps stuff.
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation
struct GoogMapView: View {
var body: some View {
GoogMapControllerRepresentable()
}
}
class GoogMapController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
var zoomLevel: Float = 15.0
let marker : GMSMarker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
// mapView.isHidden = true
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
return GMController()
}
func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {
}
}
Here is the function I'm putting within GMView.swift's UIViewController (GMController) that I’m trying to use to make a view appear within a sheet:
// Function to handle when a marker's infowindow is tapped
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf didTapInfoWindowOfMarker: GMSMarker) {
print("You tapped a marker's infowindow!")
return
}
Here is the view I’m trying to get to appear:
struct SortBy: View {
var body: some View {
VStack(alignment: .leading) {
Text("Sort By")
.font(.title)
.fontWeight(.black)
.padding(.trailing, 6)
Rectangle()
.fill(Color.blue)
.frame(width: 200, height: 200)
}
}
}
Does anyone know how I can get my function above to make a SwiftUI view appear within a sheet?
Use .sheet similarly like here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/
You will need:
In your UIViewControllerRepresentable, add a State object, let's say #State var showModal = false
In your parent view, Use .sheet(isPresented: $showModal) { CONTENT_VIEW(showModal: $showModal) }
In your UIViewControllerRepresentable, also add a Binding for showModal .
In your UIViewControllerRepresentable, use Coordinator to set your UIViewController and GMSMapViewDelegate
In your UIViewController, Now you can access the Binding via owner.showModal
Hope it helps. Good luck.
Related
We have a two forge viewers in one div. We has initialize those viewers same time and showing different models.
Is it possible to initialize Autodesk Forge viewer as extension for another viewer and show in that viewers different models?
There is no official extension for showing one Forge Viewer inside another, but implementing this is possible. You could reuse one of the UI components of the viewer (for example, DockingPanel, also explained in this tutorial), and host the second viewer in it.
Here's how such an extension might look like:
class MyExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this._group = null;
this._button = null;
this._panel = null;
}
load() {
return true;
}
unload() {
return true;
}
onToolbarCreated() {
this._group = new Autodesk.Viewing.UI.ControlGroup('allMyAwesomeExtensionsToolbar');
this._button = new Autodesk.Viewing.UI.Button('MyExtensionButton');
this._button.setToolTip('My Extension Button');
this._group.addControl(this._button);
this._panel = new MyPanel(this.viewer, this.viewer.container, 'MyPanel', 'My Panel Title');
this._button.onClick = (ev) => {
this._panel.setVisible(true);
};
this.viewer.toolbar.addControl(this._group);
}
}
class MyPanel extends Autodesk.Viewing.UI.DockingPanel {
constructor(viewer, container, id, title, options) {
super(container, id, title, options);
this.viewer = viewer;
this.secondViewer = null;
this.container.style.width = '640px';
this.container.style.height = '480px';
this.container.style.left = '1em';
this.container.style.top = '1em';
}
setVisible(show) {
super.setVisible(show);
if (show && !this.secondViewer) {
this.secondViewer = new Autodesk.Viewing.GuiViewer3D(this.container);
this.secondViewer.start();
Autodesk.Viewing.Document.load(
'urn:<your model urn>',
this.onDocumentLoadSuccess.bind(this),
this.onDocumentLoadFailure.bind(this)
);
}
}
onDocumentLoadSuccess(doc) {
const viewables = doc.getRoot().getDefaultGeometry();
this.secondViewer.loadDocumentNode(doc, viewables);
}
onDocumentLoadFailure(viewerErrorCode) {
console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('MyExtension', MyExtension);
I have a custom UIStoryboardSegue that works as desired in iOS12.*.
One of the destination view controller is a UITabbarController: for each tab, I have a controller embedded in a navigation controller.
Unfortunately, for iOS13.*, this does not work well: the view controller lifecycle is broken, and no call the viewXXXAppear() nor the willTransition() methods are no longer issued.
It looks like makeKeyAndVisible() has no effect?!
See at the bottom how the screen UI is puzzled below without viewWillAppear() being called.
An horrible temporary workaround
I had to pull my hairs but, I have found a fix which I make public (I had to add a navigation controller on the fly).
This messes the vc hierarchy: do you have a better solution?
public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
override public func perform() {
guard let window = UIApplication.shared.delegate?.window as? UIWindow,
let sourceView = source.view,
let destinationView = destination.view else {
super.perform()
return
}
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
destinationView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
window.insertSubview(destinationView, aboveSubview: sourceView)
// **My fix**
if #available(iOS 13,*) {
// I introduced an invisible navigation controller starting in iOS13 otherwise, my controller attached to the tabbar thru a navigation, dont work correctly, no viewXAppearis called.
let navigationController = UINavigationController.init(rootViewController: self.destination)
navigationController.isNavigationBarHidden = true
window.rootViewController = navigationController
}
else {
window.rootViewController = self.destination
}
window.makeKeyAndVisible()
}
}
I found a solution thanks to Unbalanced calls to begin/end appearance transitions with custom segue
What happens here is that the creation and attaching of the destination view controller happens twice, and the first one happens too soon.
So what you need to do is:
public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
override public func perform() {
guard let window = UIApplication.shared.delegate?.window as? UIWindow,
let sourceView = source.view,
let destinationView = destination.view else {
super.perform()
return
}
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
let mock = createMockView(view: desination.view)
window.insertSubview(mock, aboveSubview: sourceView)
//DO SOME ANIMATION HERE< MIGHT NEED TO DO mock.alpha = 0
//after the animation is done:
window.rootViewController = self.destination
mock.removeFromSuperview()
}
func createMockView(view: UIView) -> UIImageView {
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.main.scale)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImageView(image: image)
}
}
I had a similar problem on iOS 13 when performing a custom storyboard segue that replaces the rootViewController. The original code looked like this:
#interface CustomSegue : UIStoryboardSegue
#end
#implementation CustomSegue
- (void)perform {
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController *destination = (UIViewController *) self.destinationViewController;
[destination.view removeFromSuperview];
[appDelegate.window addSubview:destination.view];
appDelegate.window.rootViewController = destination;
}
#end
Removing the line [appDelegate.window addSubview:destination]; fixed the problem to me. Apparanently, it was unnecessary to add the new VC's view as a subview to the window. It did the job correctly even after removing that line, and it also fixed the error message "unbalanced calls to begin/end appearance transitions".
I wrote this code in Swift 3.0 but draw method is not called from func drawBorder by needsDisplay = true; drawBorder is called by clicking button in another view.
Thanks for any hint.
class clsDrawView: NSView {
private var redraw = false
var border = NSBezierPath()
var color = NSColor()
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
if redraw {
color.setStroke()
border.stroke()
}
}
func drawBorder() {
redraw = true
color = NSColor.blue
border.lineWidth = CGFloat(10)
border.move(to: NSPoint(x: 20, y: 20))
border.line(to: NSPoint(x: 50, y: 50))
needsDisplay = true
}
}
Try to call setNeedsDisplay(_:) method on your view, this should call draw(_:) method. Never call draw(_:) method by yourself.
Finally I solved it via notification
class clsDrawView: NSView {
private var draw = false
private var border = NSBezierPath()
var color = NSColor()
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NotificationCenter.default.addObserver(self, selector: #selector(self.drawBorder(_:)), name: NSNotification.Name(rawValue: "drawBorder"), object: nil)
// Drawing code here.
if draw {
color.setStroke()
border.stroke()
}
}
func drawBorder(_ notification: NSNotification) {
draw = true
color = NSColor.black
NSBezierPath.setDefaultLineWidth(4)
border.lineWidth = CGFloat(10)
border = NSBezierPath(rect: self.bounds)
needsDisplay = true
}
}
Notification is postec from ViewController like this
#IBAction func btnDraw(_ sender: NSButton) {
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "drawBorder"), object: sender)
}
I am working on tvOS app .I have a list of videos and before play main video need to play DFP ad with skip functionality.
I have added skip button on the AVPlayerViewController's view and manage focus(move focus skip button to AVPlayerViewController's view). Focus is working fine if i added AVPlayerViewController as a childViewController but in main video play/pause functionality not working.
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
If i have added AVPlayerViewController as ParentViewController then focus is not working.
self.view.addSubview(playerController.view)
playerController.didMoveToParentViewController(self)
I have implemented blow code
let playerController = AVPlayerViewController()
var playerObj:AVPlayer!
var asset:AVAsset!
var playerItem:AVPlayerItem!
override func viewDidLoad() {
super.viewDidLoad()
playerController.view.frame = CGRectMake(0.0, 0.0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
self.preferredFocusedView
self.updateFocusIfNeeded()
}
func playVideo(videoURL:String) {
self.playerController.showsPlaybackControls = false
self.asset = AVAsset(URL: NSURL(string: videoURL)!) as AVAsset
self.playerItem = AVPlayerItem(asset: self.asset)
self.playerObj = AVPlayer(playerItem: self.playerItem)
self.playerController.player = self.playerObj
self.playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VideoPlayerViewController.playerItemDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: self.playerObj.currentItem)
if !self.controlFlag{
// For main video
playFlag = true
self.playerController.showsPlaybackControls = true
}else{
// for DFP ad
playFlag = false
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VideoPlayerViewController.playerStalledNotification(_:)), name: AVPlayerItemPlaybackStalledNotification, object: self.playerObj.currentItem)
}
self.playerController.player!.play()
}
override var preferredFocusedView: UIView? {
get {
if skipAdsButton != nil {
print("focus on skip button")
return self.skipAdsButton
}else{
print("focus on super.preferredFocusedView")
return super.preferredFocusedView
}
}
}
How to fix this issue. Please suggest it.
So I have looked around, and I can't find what I am doing wrong, I am running Swift 1.2, and using SpriteKit by the way.
//showing you just in case
import SpriteKit
//My variables
var countDownLabel = SKLabelNode(fontNamed: "AmericanTypewriter-Light")
var time = 4
var timer = NSTimer()
//Don't want to give you my entire project, I know I can't override a function outside of its class
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if playButton.containsPoint(location) {
//I want this to run 4 times, but is only running once
func countdown() {
time--
countDownLabel.text = "\(time)"
}
//This is set to run my function every second for ever.
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector(countdown()), userInfo: nil, repeats: true)
playButton.removeFromParent()
countDownLabel.fontColor = SKColor.blackColor()
countDownLabel.fontSize = 70
self.addChild(countDownLabel)
}
}
I want my function countdown to run 4 times, but It is only running once for some reason, even though its set to run forever, I am also not getting any error or warnings, sorry if obvious, and if code is hard to understand, I just included the function it was all in.
'Selector(countdown())' is evaluated to nil. So,
1.Move 'countdown' function to outside of touchesBegin.
2.Use 'selector: "countdown"' instead of 'Selector(countdown())'
3.Invalidate your timer after called four times.
var time = 4
var timer = NSTimer()
func countdown() {
if --time == 0 { timer.invalidate() }
println( "\(time)" )
}
func X() {
timer = NSTimer.scheduledTimerWithTimeInterval( 1, target: self, selector: "countdown", userInfo: nil, repeats: true)
}