How can I disable auto-zooming when using userTrackingMode? - xcode

I am trying to zoom out of a map but it keeps zooming back in because of the .userTrackingMode that is set to .follow.
Is there a way to prevent or to write your own tracking mode method?
I tried to disable the tracking for 20seconds or so, but I ended up with the same problem. I also tried to just use the camera by sending it of to the users location but that did not work either.
Here is my code:
import SwiftUI
import Combine
import MapKit
struct MyMapView: UIViewRepresentable {
#State var locationManager = CLLocationManager()
#Binding var span: Double
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MyMapView
init(_ parent: MyMapView) {
self.parent = parent
super.init()
}
}
func makeUIView(context: Context) -> MKMapView {
setupManager()
let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.mapType = MKMapType.satellite
mapView.showsUserLocation = true
mapView.showsCompass = false
mapView.isZoomEnabled = true
mapView.delegate = context.coordinator
// Version 1
mapView.userTrackingMode = .follow
/*
// Version 2
let camera1 = MKMapCamera(
lookingAtCenter: mapView.centerCoordinate,
fromEyeCoordinate: mapView.centerCoordinate,
eyeAltitude: span)
mapView.setCamera(camera1, animated: false)
*/
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// Version 1 -- zoom out of the map over the span variable
uiView.setRegion(MKCoordinateRegion(center: uiView.centerCoordinate, latitudinalMeters: CLLocationDistance(span), longitudinalMeters: CLLocationDistance(span)), animated: false)
// Deactivating the tracking and activating the tracking after 60sec and then disable it again.
uiView.userTrackingMode = .none
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) {
uiView.userTrackingMode = .follow
}
uiView.userTrackingMode = .none
/*
// Version 2 -- Camera
let camera2 = MKMapCamera(
lookingAtCenter: uiView.centerCoordinate,
fromEyeCoordinate: uiView.centerCoordinate,
eyeAltitude: span)
uiView.setCamera(camera2, animated: false)
*/
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func setupManager() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
}
Is there a solution for my problem?

Related

Search for places/ locations using MapKit and Search Bar (SwiftUI, Xcode 12.4)

I have a question about how one can connect a Search Bar with MapKit, so that it is able to search for places/ locations (not using StoryBoard). I have already written the code for the Search Bar and for the MapView in separate files, but even after trying literally every code and tutorial on the internet, I couldn't find a way to connect the Search Bar to search for locations. Below one can see respectively the used SearchBar.swift file, the MapViewController.swift and a snippet of the ContentView.swift.
SearchBar.swift
import UIKit
import Foundation
import SwiftUI
import MapKit
struct SearchBar: UIViewRepresentable {
// Binding: A property wrapper type that can read and write a value owned by a source of truth.
#Binding var text: String
// NSObject: The root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects.
// UISearchBarDelegate: A collection of optional methods that you implement to make a search bar control functional.
class Coordinator: NSObject, UISearchBarDelegate {
#Binding var text: String
let Map = MapViewController()
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
text = ""
searchBar.showsCancelButton = true
searchBar.endEditing(true)
searchBar.resignFirstResponder()
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.showsCancelButton = true
searchBar.searchBarStyle = .minimal
//searchBar.backgroundColor = .opaqueSeparator
searchBar.showsCancelButton = true
return searchBar
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.text = text
}
}
MapViewController.swift
class MapViewController: UIViewController, CLLocationManagerDelegate {
let mapView = MKMapView()
let locationManager = CLLocationManager()
#Published var permissionDenied = false
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
checkLocationServices()
}
func setupMapView() {
view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
mapView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mapView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
mapView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let span = MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
guard let location = locations.last else { return }
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
let categories:[MKPointOfInterestCategory] = [.cafe, .restaurant]
let filters = MKPointOfInterestFilter(including: categories)
mapView.pointOfInterestFilter = .some(filters)
// Enables the scrolling around the user location without hopping back
locationManager.stopUpdatingLocation()
}
func checkLocalAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
permissionDenied.toggle()
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Show alert
break
case .authorizedAlways:
break
#unknown default:
fatalError()
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocalAuthorization()
} else {
// user did not turn it on
}
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocalAuthorization()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
The methods are then called in the ContentView.swift, using these methods:
struct MapViewRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return MapViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
struct ContentView: View {
#State private var searchText : String = ""
var body: some View {
ZStack(alignment: .top) {
MapViewRepresentable()
.edgesIgnoringSafeArea(.all)
.onTapGesture {
self.endTextEditing()
}
SearchBar(text: $searchText)
}
}
}
Is it possible to connect both like I explained, or is there another method you advice? I really hope you guys can help me! Thanks in advance :)

In SwiftUI, how can I add a video on loop as a fullscreen background image?

I have a video thats around 10 seconds long that I'd like to play on a loop as a fullscreen background image in one of my SwiftUI Views. How can I implement this?
First idea was working with Swift's import AVFoundation, but not sure if this is the right path.
You can use the AV family of frameworks and UIViewRepresentable to do this:
import SwiftUI
import AVKit
struct PlayerView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
In order for the video to loop I have added an observer and set the actionAtItemEnd to .none to support looping.
When the video reaches the end it will execute the playerItemDidReachEnd(...) method and seek to the beginning of the video and keep looping.
The example points to a remote video URL. If you want to point to a file within your application you can use Bundle.main.url to do so instead:
if let fileURL = Bundle.main.url(forResource: "IMG_2770", withExtension: "MOV") {
let player = AVPlayer(url: fileURL)
// ...
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
let player = AVPlayer(url: url)
player.actionAtItemEnd = .none
player.play()
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
layer.addSublayer(playerLayer)
}
#objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: .zero, completionHandler: nil)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
PlayerView()
.edgesIgnoringSafeArea(.all)
}
}
}
}
SwiftUI
As someone completely new to swift and for anyone who doesn't want to spend hours debugging this like I did. My use case was trying to create a login screen with a video playing in the background. I was struggling with the looping not working and then with the video stopping after a few seconds and starting again after the duration. This works for me.
Add a new view:
import SwiftUI
import AVKit
import AVFoundation
struct WelcomeVideo: View {
var body: some View {
WelcomeVideoController()
}
}
struct WelcomeVideo_Previews: PreviewProvider {
static var previews: some View {
WelcomeVideo()
}
}
final class WelcomeVideoController : UIViewControllerRepresentable {
var playerLooper: AVPlayerLooper?
func makeUIViewController(context: UIViewControllerRepresentableContext<WelcomeVideoController>) ->
AVPlayerViewController {
let controller = AVPlayerViewController()
controller.showsPlaybackControls = false
guard let path = Bundle.main.path(forResource: "welcome", ofType:"mp4") else {
debugPrint("welcome.mp4 not found")
return controller
}
let asset = AVAsset(url: URL(fileURLWithPath: path))
let playerItem = AVPlayerItem(asset: asset)
let queuePlayer = AVQueuePlayer()
// OR let queuePlayer = AVQueuePlayer(items: [playerItem]) to pass in items
playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
queuePlayer.play()
controller.player = queuePlayer
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<WelcomeVideoController>) {
}
}
Then attach it to a view background:
.background(WelcomeVideo())
NOTE:
Make sure your video is imported to your project
Update the name of the video to what you need or refactor slightly to pass it in
Cheers!
This is what worked for me:
source
var body: some View {
ZStack{
HStack{
Spacer()
.frame(width: 50)
AmbienceVid()
}
.edgesIgnoringSafeArea(.all)
}
}
struct AmbienceVid: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<AmbienceVid>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
class PlayerUIView: UIView {
private var playerLooper: AVPlayerLooper?
private var playerLayer = AVPlayerLayer()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
// Load the resource
let fileUrl = Bundle.main.url(forResource: "ambiencevid", withExtension: "mp4")!
let asset = AVAsset(url: fileUrl)
let item = AVPlayerItem(asset: asset)
// Setup the player
let player = AVQueuePlayer()
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill
layer.addSublayer(playerLayer)
// Create a new player looper with the queue player and template item
playerLooper = AVPlayerLooper(player: player, templateItem: item)
// Start the movie
player.play()
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
A looping, no-controls macOS implementation if people were searching for it.
import SwiftUI
import AVKit
struct NSVideoPlayer: NSViewRepresentable {
var videoURL: URL
func makeNSView(context: Context) -> AVPlayerView {
let item = AVPlayerItem(url: videoURL)
let queue = AVQueuePlayer(playerItem: item)
context.coordinator.looper = AVPlayerLooper(player: queue, templateItem: item)
let view = AVPlayerView()
view.player = queue
view.controlsStyle = .none
view.player?.playImmediately(atRate: 1)
return view
}
func updateNSView(_ nsView: AVPlayerView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
var looper: AVPlayerLooper? = nil
}
}
Tested in Swift 5 and SwiftUI 3
Viewmodel class functions
var avPlayer = AVPlayer()
func previewPlayer() -> AVPlayer {
self.avPlayer = AVPlayer(url: vedioData.preWithWithDecoURL!)
return self.avPlayer
}
func loopCurrentVedio() {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { _ in
self.avPlayer.seek(to: .zero)
self.avPlayer.play()
}
}
In your SwiftUI View class
VideoPlayer(player: previewPlayer())
.frame(width: 300, height: 532, alignment: .center)
.cornerRadius(20)
.onAppear {
loopCurrentVedio()
}
This is the simplest solution I found

How to update a Status Item created by AppDelegate from NSViewController

I'm trying to create a Countdown Timer application that runs in the Menu Bar, with no window or dock icon. I've been building this off of mostly tutorials I find online and I know the code is kind of messy (I plan to clean up after it functions properly). The issue I'm running into. In the AppDelegate I create the StatusBar item with no issue, but I can't figure out how to update it from the viewController. It instead is creating a new StatusBar item.
//AppDelegate info
class AppDelegate: NSObject, NSApplicationDelegate
{
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification)
{
menuBarRefresh(self)
}
func menuBarRefresh(_ sender: Any?)
{
if let button = item.button
{
button.image = NSImage(named: NSImage.Name("2"))
//button.title = initialTime.stringValue
button.action = #selector(togglePopover(_:))
}
popover.contentViewController = TimerViewController.freshController()
}
#objc func togglePopover(_ sender: Any?)
{
if popover.isShown
{
closePopover(sender: sender)
}
else
{
showPopover(sender: sender)
}
}
func showPopover(sender: Any?)
{
if let button = item.button
{
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
func closePopover(sender: Any?)
{
popover.performClose(sender)
}
//Controller code
import Cocoa
import AVFoundation
//Checking to ensure entered data is numeric
extension String
{
var isNumeric: Bool
{
let range = self.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted)
return (range == nil)
}
}
class TimerViewController: NSViewController
{
//Here's the texts fields for the user to enter content.
#IBOutlet var hourInput: NSTextField!
#IBOutlet var minuteInput: NSTextField!
#IBOutlet var secondInput: NSTextField!
//This is the label used to display the counter
#IBOutlet var initialTime: NSTextField!
//Here are the variables we're going to need
var hours = Int() //Place holder for the hours
var minutes = Int() //Place holder for the hours
var seconds = Int() //Place holder for the hours
var timer = Timer() //The timer we'll use later
var audioPlayer = AVAudioPlayer() //The audio player
var timeRemaining = Int() //Place holder for the total 'seconds' to be counted
var firstRun = Bool()
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
override func viewDidLoad()
{
super.viewDidLoad()
getData() //Pull last saved time from Core Data and load it.
hourInput.stringValue = "\(hours)" //Loading the hours into the hours field
minuteInput.stringValue = "\(minutes)" //Loading the minutes into the minutes field
secondInput.stringValue = "\(seconds)" //Loading the seconds into the seconds field
initialTime.stringValue = "00:00:00" //Resetting the 'counter' to 0
firstRun = true
updateStatusBar(self)
//Here we load up the audio file for the 'done' chime. If not available we print the catch
do
{
let audioPath = Bundle.main.path(forResource: "Done", ofType: "m4a")
try audioPlayer = AVAudioPlayer(contentsOf: URL(fileURLWithPath: audioPath!))
}
catch
{
print("No Joy")
}
/* if let button = item.button
{
button.image = NSImage(named: NSImage.Name("2"))
button.title = initialTime.stringValue
button.action = #selector(togglePopover(_:))
}
*/ }
}
// MARK: Storyboard instantiation
extension TimerViewController
{
static func freshController() -> TimerViewController
{
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
let identifier = NSStoryboard.SceneIdentifier("TimerViewController")
guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? TimerViewController
else
{
fatalError("Why can't I find TimerViewController? - Check Main.storyboard")
}
return viewcontroller
}
}
//Button actions follow
extension TimerViewController
{
#IBAction func clearButton(_ sender: Any)
{
clearFields()
timer.invalidate()
audioPlayer.stop()
}
#IBAction func pauseButton(_ sender: Any)
{
timer.invalidate()
}
#IBAction func quitButton(_ sender: Any)
{
exit(0)
}
#IBAction func startButton(_ sender: Any)
{
grabData()
setData()
timeRemaining = (hours*3600)+(minutes*60)+seconds
if timeRemaining <= 0
{
initialTime.stringValue = "Enter Time"
}
else
{
displayTime()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.startCountDown), userInfo: nil, repeats: true)
clearFields()
updateStatusBar(self)
}
}
}
//MARK: Other Functions
extension TimerViewController
{
func displayTime()
{
let secondsDisplay = String(format: "%02d", (timeRemaining%60))
let minutesDisplay = String(format: "%02d", (timeRemaining%3600)/60)
initialTime.stringValue = "\(timeRemaining/3600):\(minutesDisplay):\(secondsDisplay)"
}
func grabData()
{
hours = hourInput.integerValue
minutes = minuteInput.integerValue
seconds = secondInput.integerValue
}
func clearFields()
{
hourInput.stringValue = ""
minuteInput.stringValue = ""
secondInput.stringValue = ""
initialTime.stringValue = "00:00:00"
}
func setData()
{
setHour()
setMinute()
setSecond()
}
func getData()
{
getHour()
getMinute()
getSecond()
}
#objc func showTimer(_ sender: Any?)
{
print("Are we here")
}
#objc func startCountDown()
{
timeRemaining -= 1
displayTime()
updateStatusBar(self)
print(timeRemaining)
if timeRemaining == 0
{
timer.invalidate()
audioPlayer.play()
}
}
/* func setNeedsStatusBarAppearanceUpdate()
{
button.image = NSImage(named: NSImage.Name("2"))
button.action = #selector(showTimer(_:))
}
*/
func updateStatusBar(_ sender: Any?)
{
if let button = item.button
{
button.image = NSImage(named: NSImage.Name("2"))
button.action = #selector(showTimer(_:))
button.title = initialTime.stringValue
}
//let menu = NSMenu()
//menu.addItem(NSMenuItem(title: "Clear Timer", action: #selector(AppDelegate.theDv2), keyEquivalent: "R"))
//menu.addItem(NSMenuItem(title: "Quit Timer", action: #selector(AppDelegate.quit), keyEquivalent: "Q"))
//item.menu = menu
}
}
//There's a bunch of CoreData stuff after here but I left that out. I'm just using CoreData mainly to learn how to and functional reason is to store and load the last used time
As it currently works, I get two StatusBar items instead of creating one with the AppDelegate then updating that one from the ViewController.
Yup... Id-10-t error here. Just had to declare 'item' outside the class and all is well. After getting some good sleep and time away from the computer I realized I was not declaring 'item' globally.

Not able to Rotate my 3D objects

I had created one project using ARKit and SceneKit framework. In which I am working with file extension .dae, the files are locally available in my project as shown in below screenshot.
Here I had applied many gestures on this virtual object such as Tap Gesture(When I tap on camera screen, it places the virtual object there), same way Pinch Gesture and Pan Gesture. All of these gestures are working perfectly fine. Now I wanted to apply rotation gesture, for which I got stuck how to do that, also I am not getting any such available sources to achieve this.
Below is my working code so far,
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
private var movedObject: SCNNode?
private var hud :MBProgressHUD!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.autoenablesDefaultLighting = true
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
registerGestureRecognizers()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
private func registerGestureRecognizers() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(recognizer:)))
tapGestureRecognizer.numberOfTapsRequired = 1
self.sceneView.addGestureRecognizer(tapGestureRecognizer)
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinched(recognizer:)))
self.sceneView.addGestureRecognizer(pinchGestureRecognizer)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveObject(recognizer:)))
panGestureRecognizer.maximumNumberOfTouches = 1
panGestureRecognizer.minimumNumberOfTouches = 1
self.sceneView.addGestureRecognizer(panGestureRecognizer)
let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotateObject(recognizer:)))
self.sceneView.addGestureRecognizer(rotationGestureRecognizer)
}
#objc func pinched(recognizer :UIPinchGestureRecognizer) {
if recognizer.state == .changed {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = self.sceneView.hitTest(touch, options: nil)
if let hitTest = hitTestResults.first {
let chairNode = hitTest.node
let pinchScaleX = Float(recognizer.scale) * chairNode.scale.x
let pinchScaleY = Float(recognizer.scale) * chairNode.scale.y
let pinchScaleZ = Float(recognizer.scale) * chairNode.scale.z
chairNode.scale = SCNVector3(pinchScaleX,pinchScaleY,pinchScaleZ)
recognizer.scale = 1
}
}
}
#objc func moveObject(recognizer: UIPanGestureRecognizer) {
print("Move object")
if recognizer.state == .began {
print("Pan state began")
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let result = sceneView.hitTest(tapPoint ?? CGPoint.zero, options: nil)
if result.count == 0 {
return
}
let hitResult: SCNHitTestResult? = result.first
if (hitResult?.node.name == "free_car_1") {
movedObject = hitResult?.node
} else if (hitResult?.node.parent?.name == "free_car_1") {
movedObject = hitResult?.node.parent
}
if (movedObject != nil) {
print("Holding an Object")
}
}
if recognizer.state == .changed {
print("Pan State Changed")
if (movedObject != nil) {
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(tapPoint ?? CGPoint.zero, types: .featurePoint)
let result: ARHitTestResult? = hitResults.last
let matrix: SCNMatrix4 = SCNMatrix4((result?.worldTransform)!)
//SCNMatrix4FromMat4((result?.worldTransform)!)
let vector: SCNVector3 = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
movedObject?.position = vector
print("Moving object position")
}
}
if recognizer.state == .ended {
print("Done moving object homeie")
movedObject = nil
}
}
#objc func tapped(recognizer :UITapGestureRecognizer) {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = sceneView.hitTest(touch)
guard let hitTest = hitTestResults.first?.node else {
let hitTestResultsWithExistingPlane = sceneView.hitTest(touch, types: .existingPlane)
let chairScene = SCNScene(named: "ShelbyWD.dae")!
guard let chairNode = chairScene.rootNode.childNode(withName: "ShelbyWD", recursively: true) else {
return
}
if let hitTestAvailable = hitTestResultsWithExistingPlane.first {
chairNode.position = SCNVector3(hitTestAvailable.worldTransform.columns.3.x,hitTestAvailable.worldTransform.columns.3.y,hitTestAvailable.worldTransform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(chairNode)
return
}
return
}
hitTest.removeFromParentNode()
}
#objc func rotateObject(recognizer :UIRotationGestureRecognizer)
{
}
}
Can anyone help me out to apply rotation gesture on my object?
Thank you!
In order to rotate an SCNNode, the 1st thing you need to do, is create a variable to store the rotationAngle around the YAxis or any other that you wish to perform the rotation on e.g:
var currentAngleY: Float = 0.0
Then have some way to have detected to node you wish to rotate, which in my example I am calling currentNode e.g.
var currentNode: SCNNode!
In my example I will just rotate around the YAxis.
You can use a UIPanGestureRecognizer like so:
/// Rotates An Object On It's YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateObject(_ gesture: UIPanGestureRecognizer) {
guard let nodeToRotate = currentNode else { return }
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
nodeToRotate.eulerAngles.y = newAngleY
if(gesture.state == .ended) { currentAngleY = newAngleY }
print(nodeToRotate.eulerAngles)
}
Or if you wish to use a UIRotationGesture you can do something like this:
/// Rotates An SCNNode Around It's YAxis
///
/// - Parameter gesture: UIRotationGestureRecognizer
#objc func rotateNode(_ gesture: UIRotationGestureRecognizer){
//1. Get The Current Rotation From The Gesture
let rotation = Float(gesture.rotation)
//2. If The Gesture State Has Changed Set The Nodes EulerAngles.y
if gesture.state == .changed{
isRotating = true
currentNode.eulerAngles.y = currentAngleY + rotation
}
//3. If The Gesture Has Ended Store The Last Angle Of The Cube
if(gesture.state == .ended) {
currentAngleY = currentNode.eulerAngles.y
isRotating = false
}
}
Hope it helps...

How to show route between a MKPointAnnotation and user's current location in swift 2

I am trying to show the route between a MKPointAnnotation and user's current location, but i am fail with it.
My idea is: getting user's current location -> getting the MKPointAnnotation' Coordinate -> line up with MKPolylineRenderer
The problem is that i cannot find the problem. :( I have no idea where i should modify.
class MapInSearch: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var destination: MKMapItem?
var coords: CLLocationCoordinate2D?
let locationManager = CLLocationManager()
var PlaceLat = ""
var PlaceLong = ""// get from previous view controller
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}// step 1
self.mapView.showsUserLocation = true
self.mapView.delegate = self
self.addRoute() // step 2
}
func addRoute() {
var pointsToUse: [CLLocationCoordinate2D] = []
if PlaceLat != "" || PlaceLong != "" {
let coords = "\(PlaceLat), \(PlaceLong)"
let p = CGPointFromString(coords)
pointsToUse += [CLLocationCoordinate2DMake(CLLocationDegrees(p.x), CLLocationDegrees(p.y))]
}
pointsToUse += [CLLocationCoordinate2DMake(CLLocationDegrees(coords!.latitude), CLLocationDegrees(coords!.longitude))]
let myPolyline = MKPolyline(coordinates: &pointsToUse, count: 2)
mapView.addOverlay(myPolyline)
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let lineView = MKPolylineRenderer(overlay: overlay)
lineView.strokeColor = UIColor.greenColor()
return lineView // step 3
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.coords = manager.location!.coordinate
print("locations = \(coords!.latitude) \(coords!.longitude)")
}
My code is very disorderly because i mixed 4-5 tutorials. Also, these tutorials is written with swift 1.2.(i have tried to edit it to swift 2, but i am fail)
Did you ever resolve your problem? Using the latest iteration of Swift 2 in XCode 7.3, in your view (we will call it MyViewController):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.mapView.delegate = self
var coordinates : [CLLocationCoordinate2D] = [];
addRoute(coordinates);
}
func addRoute(coordinates: [CLLocationCoordinate2D]) {
// insert your code to populate coordinates array with your coordinates
polyLine = MKPolyline(coordinates: &coordinates, count: coordinates.count)
self.mapView.addOverlay(polyLine, level: MKOverlayLevel.AboveRoads)
}
Then in the same file:
extension MyViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let pr = MKPolylineRenderer(overlay: overlay);
pr.strokeColor = UIColor.blueColor().colorWithAlphaComponent(0.5);
pr.lineWidth = 5;
return pr;
}
}
You may find the important part was the extension. I haven't tested this code, so feel free to correct any issues that crept in.
in your CLLocationManagerDelegate delegate function didUpdateLocations you can update your location by setting
self.myLocation = locations[0] as CLLocation
Then call MakeRoute() - This is a function i wrote to either make a route by car or by walking (hence the self.driveIsSet)
func makeRoute() {
let startPlaceMark = MKPlacemark(coordinate: myLocation.coordinate)
let endPlaceMark = MKPlacemark(coordinate: restLocation.coordinate)
let startMapItem = MKMapItem(placemark: startPlaceMark)
let endMapItem = MKMapItem(placemark: endPlaceMark)
let directionRequest = MKDirectionsRequest()
directionRequest.source = startMapItem
directionRequest.destination = endMapItem
if self.driveIsSet {
directionRequest.transportType = .automobile
} else {
directionRequest.transportType = .walking
}
let directions = MKDirections(request: directionRequest)
directions.calculate { (routeResponse, routeError) in
guard let routeResponse = routeResponse else {
if let routeError = routeError {
print(routeError)
}
return
}
self.mapView.removeOverlays(self.mapView.overlays)
let route = routeResponse.routes[0]
self.mapView.add(route.polyline, level: .aboveRoads)
}
}

Resources