app crashed when 'startDeviceMotionUpdatesToQueue' of CMMotionManager callback - ios

In my project, I have used CoreMotion, my code is in img. sometimes it crashed, but I can't repeat it, and I also don't know why.. could someone help me? I have been tortured by it for a long time...
startMotionManager:

Please try this code
import CoreMotion
var motionManager: CMMotionManager?
override func viewDidLoad() {
super.viewDidLoad()
motionManager = CMMotionManager()
if motionManager?.isDeviceMotionAvailable == true {
motionManager?.deviceMotionUpdateInterval = 0.1;
let queue = OperationQueue()
motionManager?.startDeviceMotionUpdates(to: queue, withHandler: { [weak self] (motion, error) -> Void in
// Get the attitude of the device
if let attitude = motion?.attitude {
// Get the pitch (in radians) and convert to degrees.
// Import Darwin to get M_PI in Swift
print(attitude.pitch * 180.0/M_PI)
}
})
print("Device motion started")
}
else {
print("Device motion unavailable");
}
// Do any additional setup after loading the view, typically from a nib.
}
based on attitude.pitch you can know Device angle and rotation on ( + & - )

Related

Swift 3 device motion gravity

I have an iPhone application with a level in it that is based on the gravityY parameter of a device motion call to motionmanager. I have fixed the level to the pitch of the phone, as I wish to show the user whether the phone is elevated or declined relative to a flat plane (flat to the ground) through its x-axis...side to side or rotated is not relevant. To do that, I have programmed the app to slide an indicator (red when out of level) along the level (a bar)....its maximum travel is each end of the level.
The level works great...and a correct value is displayed, until the user locks the phone and puts it in his or her back pocket. While in this stage, the level indicator shifts to one end of the level (the end of the phone that is elevated in the pocket), and when the phone is pulled out and unlocked, the app does not immediately restore the level - it remains out of level, even if I do a manual function call to restore the level. After about 5 minutes, the level seems to restore itself.
Here is the code:
func getElevation () {
//now get the device orientation - want the gravity value
if self.motionManager.isDeviceMotionAvailable {
self.motionManager.deviceMotionUpdateInterval = 0.05
self.motionManager.startDeviceMotionUpdates(
to: OperationQueue.current!, withHandler: {
deviceMotion, error -> Void in
var gravityValueY:Double = 0
if(error == nil) {
let gravityData = self.motionManager.deviceMotion
let gravityValueYRad = (gravityData!.gravity.y)
gravityValueY = round(180/(.pi) * (gravityValueYRad))
self.Angle.text = "\(String(round(gravityValueY)))"
}
else {
//handle the error
self.Angle.text = "0"
gravityValueY = 0
}
var elevationY = gravityValueY
//limit movement of bubble
if elevationY > 45 {
elevationY = 45
}
else if elevationY < -45 {
elevationY = -45
}
let outofLevel: UIImage? = #imageLiteral(resourceName: "levelBubble-1")
let alignLevel: UIImage? = #imageLiteral(resourceName: "levelBubbleGR-1")
let highElevation:Double = 1.75
let lowElevation:Double = -1.75
if highElevation < elevationY {
self.bubble.image = outofLevel
}
else if elevationY < lowElevation {
self.bubble.image = outofLevel
}
else {
self.bubble.image = alignLevel
}
// Move the bubble on the level
if let bubble = self.bubble {
UIView.animate(withDuration: 1.5, animations: { () -> Void in
bubble.transform = CGAffineTransform(translationX: 0, y: CGFloat(elevationY))
})
}
})
}
}
I would like the level to restore almost immediately (within 2-3 seconds). I have no way to force calibration or an update. This is my first post....help appreciated.
Edit - I have tried setting up a separate application without any animation with the code that follows:
//
import UIKit
import CoreMotion
class ViewController: UIViewController {
let motionManager = CMMotionManager()
#IBOutlet weak var angle: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func startLevel(_ sender: Any) {
startLevel()
}
func startLevel() {
//now get the device orientation - want the gravity value
if self.motionManager.isDeviceMotionAvailable {
self.motionManager.deviceMotionUpdateInterval = 0.1
self.motionManager.startDeviceMotionUpdates(
to: OperationQueue.current!, withHandler: {
deviceMotion, error -> Void in
var gravityValueY:Double = 0
if(error == nil) {
let gravityData = self.motionManager.deviceMotion
let gravityValueYRad = (gravityData!.gravity.y)
gravityValueY = round(180/(.pi) * (gravityValueYRad))
}
else {
//handle the error
gravityValueY = 0
}
self.angle.text = "(\(gravityValueY))"
})}
}
}
Still behaves exactly the same way....
OK....so I figured this out through trial and error. First, I built a stopGravity function as follows:
func stopGravity () {
if self.motionManager.isDeviceMotionAvailable {
self.motionManager.stopDeviceMotionUpdates()
}
}
I found that the level was always set properly if I called that function, for example by moving to a new view, then restarting updates when returning to the original view. When locking the device or clicking the home button, I needed to call the same function, then restart the gravity features on reloading or returning to the view.
To do that I inserted the following in the viewDidLoad()...
NotificationCenter.default.addObserver(self, selector: #selector(stopGravity), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(stopGravity), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
This notifies the AppDelegate and runs the function. That fixed the issue immediately.

How to detect if iPhone is in motion? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I am trying to create an app (in swift) that will detect if a iPhone is in motion.
How would I go to approaching this?
You should look into getting the data from the accelerometer.
Here is an example of, how you could retrieve the information.
let motionManager = CMMotionManager()
if motionManager.isAccelerometerAvailable {
let queue = OperationQueue()
motionManager.startAccelerometerUpdates(to: queue, withHandler:
{
data, error in guard let data = data else { return }
print("X = \(data.acceleration.x)")
print("Y = \(data.acceleration.y)")
print("Z = \(data.acceleration.z)")
}
)
} else {
print("Accelerometer is not available")
}
Dont forget to import CoreMotion!
Updated solution
Here is a working example of how to get the accelerometer data, if the device is moving with over 10 km/h.
import UIKit
import CoreMotion
import CoreLocation
class ViewController: UIViewController {
var timer = Timer()
var locationManager = CLLocationManager()
let motionManager = CMMotionManager()
override func viewDidLoad() {
super.viewDidLoad()
scheduledTimerWithTimeInterval() //Calling function with timer
// Do any additional setup after loading the view, typically from a nib.
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function **getSpeed** with the interval of 1 seconds
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.getSpeed), userInfo: nil, repeats: true)
}
func getSpeed(){
var speed: CLLocationSpeed = CLLocationSpeed()
speed = Double((locationManager.location?.speed)!)
print(String(format: "%.0f km/h", speed * 3.6)) //Current speed in km/h
//If speed is over 10 km/h
if(speed * 3.6 > 10 ){
//Getting the accelerometer data
if motionManager.isAccelerometerAvailable{
let queue = OperationQueue()
motionManager.startAccelerometerUpdates(to: queue, withHandler:
{data, error in
guard let data = data else{
return
}
print("X = \(data.acceleration.x)")
print("Y = \(data.acceleration.y)")
print("Z = \(data.acceleration.z)")
}
)
} else {
print("Accelerometer is not available")
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I am trying to create an app (in swift) that will detect if a iPhone is in motion.
That might not be possible. Let's say the iPhone is moving steadily in one direction at 3 km/hr. Core Motion cannot detect this because it is not an acceleration! So your only hope would be to use Core Location; that will give you heading and speed based on the GPS, though of course accuracy will be somewhat limited.

How to recognize a screen high-five

I have a client that wants to recognize when an user smacks their screen with their whole hand, like a high-five. I suspect that Apple won't approve this, but let's look away from that.
I though of using a four-finger-tap recognizer, but that doesn't really cover it. The best approach would possibly be to check if the user is covering at least 70% of the screen with their hand, but I don't know how to do that.
Can someone help me out here?
You could use the accelerometer to detect the impact of a hand & examine the front camera feed to find a corresponding dark frame due to the hand covering the camera*
* N.B. a human hand might not be big enough to cover the front camera on an iPhone 6+
Sort of solved it. Proximity + accelerometer works good enough. Multitouch doesn't work, as it ignores stuff it doesn't think of as taps.
import UIKit
import CoreMotion
import AVFoundation
class ViewController: UIViewController {
var lastHighAccelerationEvent:NSDate? {
didSet {
checkForHighFive()
}
}
var lastProximityEvent:NSDate? {
didSet {
checkForHighFive()
}
}
var lastHighFive:NSDate?
var manager = CMMotionManager()
override func viewDidLoad() {
super.viewDidLoad()
//Start disabling the screen
UIDevice.currentDevice().proximityMonitoringEnabled = true
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(proximityChanged), name: UIDeviceProximityStateDidChangeNotification, object: nil)
//Check for acceloremeter
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { (data, error) in
let sum = abs(data!.acceleration.y + data!.acceleration.z + data!.acceleration.x)
if sum > 3 {
self.lastHighAccelerationEvent = NSDate()
}
}
//Enable multitouch
self.view.multipleTouchEnabled = true
}
func checkForHighFive() {
if let lastHighFive = lastHighFive where abs(lastHighFive.timeIntervalSinceDate(NSDate())) < 1 {
print("Time filter")
return
}
guard let lastProximityEvent = lastProximityEvent else {return}
guard let lastHighAccelerationEvent = lastHighAccelerationEvent else {return}
if abs(lastProximityEvent.timeIntervalSinceDate(lastHighAccelerationEvent)) < 0.1 {
lastHighFive = NSDate()
playBoratHighFive()
}
}
func playBoratHighFive() {
print("High Five")
let player = try! AudioPlayer(fileName: "borat.mp3")
player.play()
}
func proximityChanged() {
if UIDevice.currentDevice().proximityState {
self.lastProximityEvent = NSDate()
}
}
}
You can detect finger count with multi touch event handling. check this answer

How to use CoreMotion in WatchKit?

I was quite dubious on this question's title phrasing, but I think that's the whole point as it is.
I've been trying to just read the CoreMotion data on the WatchKit, but as it turns out, I can't get startDeviceMotionUpdatesToQueue to work, my handler is never called.
I tried running in a custom background thread (NSOperationQueue()), still no luck.
I'm debugging on a real Apple Watch, not the simulator.
In my WKInterfaceController:
let manager = CMMotionManager()
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let communicator = SessionDelegate()
manager.deviceMotionUpdateInterval = 1 / 60
manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) {
(motionerOp: CMDeviceMotion?, errorOp: NSError?) -> Void in
print("got into handler")
guard let motion = motionerOp else {
if let error = errorOp {
print(error.localizedDescription)
}
assertionFailure()
return
}
print("passed guard")
let roll = motion.attitude.roll
let pitch = motion.attitude.pitch
let yaw = motion.attitude.yaw
let attitudeToSend = ["roll": roll, "pitch": pitch, "yaw": yaw]
communicator.send(attitudeToSend)
}
print("normal stack")
}
the output is
normal stack
normal stack
(Yes, twice! I don't know why that either, but that is not the point, must be another thing I'm doing wrongly)
I'm posting this here 'cause I have no clue to where look into, this is freaking crazy.
Device Motion (startDeviceMotionUpdatesToQueue) is not available in WatchOS2 yet (deviceMotionAvailable returns false), probably accelerometer can help you startAccelerometerUpdatesToQueue

Google Street View Rotate Camera With Device Gyroscope

I'm building a mobile app using the Google Maps SDK for iOS and I'm trying to use the mobile device's gyroscope data to pan the camera around a panorama in Street View. I've setup a GMSPanoramaView and a GMSPanoramaCamera with initial positions. I'm using the method -updateCamera on GMSPanoramaView but am unable to smoothly pan across each panorama. If anyone has any idea how I can achieve this feature please let me know. Here is my code so far in the -viewDidLoad portion of my viewcontroller:
if manager.gyroAvailable {
let queue = NSOperationQueue.mainQueue()
manager.startGyroUpdatesToQueue(queue, withHandler: { (data, error) -> Void in
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
// Update UI
let cameraUpdate = GMSPanoramaCameraUpdate.rotateBy((data?.rotationRate.x.radiansToDegrees)!)
self.panoView.updateCamera(cameraUpdate, animationDuration: 1)
})
})
}
Following code is working in swift 3
if motionManager.isGyroAvailable {
motionManager.startGyroUpdates(to: OperationQueue.main, withHandler: { (gyroData: CMGyroData?, error: Error?) in
let y = gyroData!.rotationRate.y
print("gyrodata: \(y)")
let cameraUpdate = GMSPanoramaCameraUpdate.rotate(by: -CGFloat((gyroData?.rotationRate.y)!))
panoView.updateCamera(cameraUpdate, animationDuration: 1)
})
}
Working code in Swift 4 (controls both left right and up down so wherever the phone points is where the view will be updated)
import GoogleMaps
import CoreMotion
class ViewController: UIViewController, GMSPanoramaViewDelegate {
let motionManager = CMMotionManager()
override func loadView() {
let panoView = GMSPanoramaView(frame: .zero)
panoView.delegate = self
self.view = panoView
// you can choose any latitude longitude here, this is my random choice
panoView.moveNearCoordinate(CLLocationCoordinate2D(latitude: 48.858, longitude: 2.284))
motionManager.startDeviceMotionUpdates()
if motionManager.isGyroAvailable {
motionManager.startGyroUpdates(to: OperationQueue.main, withHandler: { (gyroData: CMGyroData?, error: Error?) in
// needed to figure out the rotation
let y = (gyroData?.rotationRate.y)!
let motion = self.motionManager.deviceMotion
if(motion?.attitude.pitch != nil) {
// calculate the pitch movement (up / down) I subtract 40 just as
// an offset to the view so it's more at face level.
// the -40 is optional, can be changed to anything.
let pitchCamera = GMSPanoramaCameraUpdate.setPitch( CGFloat(motion!.attitude.pitch).radiansToDegrees - 40 )
// rotation calculation (left / right)
let rotateCamera = GMSPanoramaCameraUpdate.rotate(by: -CGFloat(y) )
// rotate camera immediately
panoView.updateCamera(pitchCamera, animationDuration: 0)
// for some reason, when trying to update camera
// immediately after one another, it will fail
// here we are dispatching after 1 millisecond for success
DispatchQueue.main.asyncAfter(deadline: .now() + 0.0001, execute: {
panoView.updateCamera(rotateCamera, animationDuration: 0)
})
}
})
}
}
}
extension BinaryInteger {
var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
And do not forget to put this in your info.plist
Privacy - Motion Usage Description
Add a proper descriptor for why you need this data in the info.plist and that should properly configure your application.
I have easy way, in my project i use "CLLocationManagerDelegate" - func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading)
self.locationManager.startUpdatingHeading() // put this line on viewdidload for example
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
self.panoramaView.animate(to: GMSPanoramaCamera(heading: newHeading.magneticHeading, pitch: newHeading.y, zoom: 1), animationDuration: 0.1)
}

Resources