Why am I not getting any accelerometer update? - ios

I am trying to get accelerometer updates with CoreMotion and Swift, here is what I placed in my viewDidLoad :
override func viewDidLoad() {
super.viewDidLoad()
let motionManager = CMMotionManager()
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue()) {
(info:CMAccelerometerData!,error:NSError!) in
if error != nil {
println(error)
}
else {
println("OK")
}
}
}
The problem is that it looks like my closure never gets called (I don't have anything in the console), do you know why?

The problem is that the variable motionManager, to which your CMMotionManager instance is assigned, is declared as a local variable (in the body of the function viewDidLoad), which means that it goes out of existence when the function finishes executing. Therefore its lifetime is about a 10000th of a second.
Well, that is not long enough for your CMMotionManager to obtain very many updates!

Related

Can two same closures on the main thread, from the same function call, crash an iOS program written in Swift?

My users experienced crashes when I sent them an update on TestFlight. After examining the eight crash reports they submitted, I've noticed a commonality - there are two of the same closures sitting on top of thread 0. Could this have caused the crash? What do you think is the cause, if not?
Please see image for crash report of thread 0. All other threads generally look the same in the report.
Note - when the users opened their app subsequent times after the initial opening, they did not experience further crashes.
Thank you in advance for your thoughts.
Update from comments, 9/29/22 -
Here's the closure code as requested by Damien and Tadreik:
When the app is opened, this line runs during initialization, which sets up the variables the connection view controller needs. Thus the empty closure.
if !twilioIDs.isEmpty {
ProfileModelManager.shared.getUsersForConnectionView(withTwilioIDs: twilioIDs) { _ in }
}
And the code below is invoked when the connection view is tapped on from the menu tab the second time:
if !twilioIDs.isEmpty {
ProfileModelManager.shared.getUsersForConnectionView(withTwilioIDs: twilioIDs) { result in
guard let success = result else { return }
if success {
self.handleSuccessOfGettingConnectionCards()
}
else {
self.handleFailureOfGettingConnectionCards()
}
}
}
Here is the code for handleSuccessOfGettingConnectionCards -
refreshControl.endRefreshing()
hideNoConnectionsLabel()
createChatViewControllersForChannels()
connectionsTableView.alpha = 1
errorConnectingLabel.alpha = 0
connectionsTableView.reloadData()
showActivitySpinner(false)
navigationController?.navigationBar.isHidden = true
Here is the code for handleFailureOfGettingConnectionCards -
showErrorConnectingMessage()
refreshControl.endRefreshing()
connectionsTableView.alpha = 0
hideNoConnectionsLabel()
showActivitySpinner(false)
Thanks in advance again for any intuition or experience you may share.
The crash log for thread 0
The code inside the closure ProfileModelManager.shared.getUsersForConnectionView(withTwilioIDs: twilioIDs) { result in captures a reference to self, your problem might be that at the time of execution, self is pointing to a null reference (has been deallocated), which can cause a crash. Try to set a weak reference to self like that and see if the crash still occurs :
if !twilioIDs.isEmpty {
ProfileModelManager.shared.getUsersForConnectionView(withTwilioIDs: twilioIDs) { [weak self] result in
guard let success = result else { return }
if success {
self?.handleSuccessOfGettingConnectionCards()
}
else {
self?.handleFailureOfGettingConnectionCards()
}
}
}
If you want you could also handle the nil case of the weak self as any other optional.

AVCaptureSession stopRunning() Mysterious Crash

Recently I got notified about a crash from firebase and this is the message:
[AVCaptureSession stopRunning] stopRunning may not be called between calls to beginConfiguration and commitConfiguration
I went through my code and the weirdest part is I never call and there is no mention nowhere of beginConfiguration() and commitConfiguration().
In my CameraManager class this is the function that triggers the crash, it called on deinit:
func stop() {
guard isStarted else { return Log.w("CameraManager wasn't started") }
queue.async {
self.isStarted = false
self.isCapturingFrame = false
self.isCapturingCode = false
self.session?.stopRunning()
self.session = nil
self.device = nil
}
notificationCenter.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
layer.removeFromSuperlayer()
layer.session = nil
}
The queue is just a serial disptach queue.
No matter what I tried, I couldn't reproduce this crash.
Tried pulling the menu, push notification, phone call, simulate memory warning etc...
Just to clarify, there is not a single place in my code calling beginConfiguration and commitConfiguration.
I could imagine that layer.session = nil will cause a re-configuration of the capture session (since the connection to the preview layer is removed). And since you are calling stopRunning() async, I guess you can run into race conditions where stopRunning() gets called right in the middle of the configuration change.
I would suggest you either try to make the cleanup calls synchronous (queue.sync { ... }) or move the layer cleanup into the async block as well.

iOS CoreMotion.MotionThread EXC_BAD_ACCESS is thrown after stopDeviceMotionUpdates() is called

I have a view controller that uses CoreMotion to monitor the device's Attitude.
Here is the handler that is used in the call to startDeviceMotionUpdates():
/**
* Receives device motion updates from Core Motion and uses the attitude of the device to update
* the position of the attitude tracker inside the bubble level view.
*/
private func handleAttitude(deviceMotion: CMDeviceMotion?, error: Error?) {
guard let attitude = deviceMotion?.attitude else {
GLog.Error(message: "Could not get device attitude.")
return
}
// Calculate the current attitude vector
let roll = attitude.roll
let pitch = attitude.pitch - optimalAngle
let magnitude = sqrt(roll * roll + pitch * pitch)
// Drawing can only happen on the main thread
DispatchQueue.main.async {
[weak self] in
guard let weakSelf = self else {
GLog.Log("could not get weak self")
return
}
// Move the bubble in the attitude tracker to match the current attitude
weakSelf.bubblePosX.constant = CGFloat(roll * weakSelf.attitudeScalar)
weakSelf.bubblePosY.constant = CGFloat(pitch * weakSelf.attitudeScalar)
// Set the border color based on the current attitude.
if magnitude < weakSelf.yellowThreshold {
weakSelf.attitudeView.layer.borderColor = weakSelf.green.cgColor
} else if magnitude < weakSelf.redThreshold {
weakSelf.attitudeView.layer.borderColor = weakSelf.yellow.cgColor
} else {
weakSelf.attitudeView.layer.borderColor = weakSelf.red.cgColor
}
// Do the actual drawing
weakSelf.view.layoutIfNeeded()
}
}
I added [weak self] to see if it would fix things, but it has not. This crash is not easy to reproduce.
When I am done the with VC that uses CoreMotion, I call stopDeviceMotionUpdates() in the VC's viewWillDisappear() method. This VC is the only class in the app that imports CoreMotion.
However, when I arrive in the next VC, occasionally I see EXC_BAD_ACCESS getting thrown on a co m.apple.CoreMotion.MotionThread.
Anybody know why CoreMotion would spawn a thread after the VC that used it has been dismissed? I've verified that the VC is no longer in memory when the crash happens. And yes, the two VCs I'm dealing with are presented modally.
I've examined the memory graph, and when the crash happens, these CoreMotion objects are being reported:
And:
I don't know if those objects should still be in memory after the instance of the CoreMotionManager has been deallocated or not. According to the memory graph, there is no instance of CoreMotionManager in memory.
The VC that imports CoreMotion also imports ARKit. Not sure if some crazy interaction between CoreMotion and ARKit is the problem.
There does seem to be something going on between the main thread and the MotionThread(14):
I'm not sure what to make of the main thread stack trace.
Sometimes when the CoreMotion VC is dismissed, I've noticed that there is a lag in the memory it uses getting released. The release always happens eventually.
Thanks to anybody who can help!
We have a ARSCNView member. We were not calling sceneView.session.pause() when we dismissed the VC that used the sceneView member. One line of code. That was it.
Are you passing the function handleAttitude direct to startDeviceMotionUpdates
as in startDeviceMotionUpdates(to:someQueue, withHandler:handleAttitude)
That will set up a retain cycle between your VC and CMMotionManager
Try
singletonMM.startDeviceMotionUpdates(to:someQueue) { [weak self] motion,error in
self?.handleAttitude(motion,error)
}
To prevent a strong ref to your VC.

Core Motion Swift Closure issue

I'm trying to convert an old game application built in obj-c to the new swift code. i'm having some issues understanding swift closures and how to use them for example in the"startAccelerometerUpdatesToQueue" method.
i have initialized the motion manager in this way
motionManager!.accelerometerUpdateInterval = (1/40)
then in the viewdidload of my view controller
var queue:NSOperationQueue
motionManager?.startAccelerometerUpdatesToQueue(queue, withHandler: {(accelerometerData : CMAccelerometerData, error : NSError) in
})
the "startAccelerometerUpdatesToQueue" is giving me an error and i'm pretty sure i didn't understand the correct closure syntax.
Any ideas?
Actually, you just got the signature wrong – the arguments to your closure need to be optionals (since they are passed from Objective-C, they could be nil). Because of that, the arguments you provide don't match an existing method signature, and because of that you get an error.
Take a look at the iOS 8 API docs, they also provide Swift signatures:
func startAccelerometerUpdatesToQueue(_ queue: NSOperationQueue!,
withHandler handler: CMAccelerometerHandler!)
and CMAccelerometerHandler is defined as
typealias CMAccelerometerHandler = (CMAccelerometerData!, NSError!) -> Void
Thus, your call should be:
motionManager?.startAccelerometerUpdatesToQueue(queue, withHandler: {(accelerometerData : CMAccelerometerData!, error : NSError!) in
})
And as with any function/method that takes a closure as it's last argument, you can leave it out of the argument list and write it after the call (trailing closure syntax – this example also leaves out the types, as they can be inferred, but that is optional):
motionManager?.startAccelerometerUpdatesToQueue(queue) { accelerometerData, error in
}
CMMotionManager
object is the gateway to the motion services provided by iOS. These services provide an app with accelerometer data, rotation-rate data, magnetometer data, and other device-motion data such as attitude. These types of data originate with a device’s accelerometers and (on some models) its magnetometer and gyroscope.
Handling Motion Updates at Specified Intervals
To receive motion data at specific intervals, the app calls a “start” method that takes an operation queue (instance of NSOperationQueue) and a block handler of a specific type for processing those updates. The motion data is passed into the block handler. The frequency of updates is determined by the value of an “interval” property.
Accelerometer.
Set the accelerometerUpdateInterval property to specify an update interval. Call the startAccelerometerUpdatesToQueue:withHandler: method, passing in a block of type CMAccelerometerHandler. Accelerometer data is passed into the block as CMAccelerometerData objects.
Gyroscope.
Magnetometer.
Device motion.
The interval, in seconds, for providing accelerometer updates to the block handler.
Declaration
SWIFT
var accelerometerUpdateInterval: NSTimeInterval
Discussion
The system supplies accelerometer updates to the block handler specified in startAccelerometerUpdatesToQueue:withHandler: at regular intervals determined by the value of this property.
The interval units are in seconds. The value of this property is capped to minimum and maximum values; the maximum value is determined by the maximum frequency supported by the hardware. If your app is sensitive to the intervals of acceleration data, it should always check the timestamps of the delivered CMAccelerometerData instances to determine the true update interval.
Availability
Available in iOS 4.0 and later.
import UIKit
import CoreMotion
class ViewController: UIViewController {
let motionManager = CMMotionManager()
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
motionManager.startAccelerometerUpdates()
motionManager.startGyroUpdates()
motionManager.startMagnetometerUpdates()
motionManager.startDeviceMotionUpdates()
timer = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
#objc func update() {
if let accelerometerData = motionManager.accelerometerData {
print(accelerometerData)
}
if let gyroData = motionManager.gyroData {
print(gyroData)
}
if let magnetometerData = motionManager.magnetometerData {
print(magnetometerData)
}
if let deviceMotion = motionManager.deviceMotion {
print(deviceMotion)
}
}
}

iOS Motion Detection: Motion Detection Sensitivity Levels

I have a simple question. I'm trying to detect when a user shakes the iPhone. I have the standard code in place to detect the motion and this works no problem. However, in testing this on my actual phone, I've realized that you have to shake the device quite hard to get the motion detection to trigger. I would like to know if there is a way to implement a level of sensitivity checking. For example, a way to detect if a user lightly shakes the device or somewhere between light and hard shake. This will be targeted towards iOS 7 so any tips or advice that is not deprecated from older iOS version would be greatly appreciated. I've done my googling but have yet to find any good solutions to this problem (If there are any.)
Thanks!
-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(motion == UIEventSubtypeMotionShake)
{
//Detected motion, do something about it
//at this point.
}
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
-(void)viewWillDisappear:(BOOL)animated
{
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
Here is the solution I found. This works well but you do have to play with the deviceMotionUpdateInterval time value as well as the accelerationThreshold which can be tricky to get a fine balancing act for a actual "light shake" vs "picking up the phone and moving it closer to your face etc..." There might be better ways but here is one to start. Inside of my view didLoad I did something like this:
#import <CoreMotion/CoreMotion.h> //do not forget to link the CoreMotion framework to your project
#define accelerationThreshold 0.30 // or whatever is appropriate - play around with different values
-(void)viewDidLoad
{
CMMotionManager *motionManager;
motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 1;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[self motionMethod:motion];
}];
}
-(void)motionMethod:(CMDeviceMotion *)deviceMotion
{
CMAcceleration userAcceleration = deviceMotion.userAcceleration;
if (fabs(userAcceleration.x) > accelerationThreshold
|| fabs(userAcceleration.y) > accelerationThreshold
|| fabs(userAcceleration.z) > accelerationThreshold)
{
//Motion detected, handle it with method calls or additional
//logic here.
[self foo];
}
}
This is a swift version based on zic10's answer, with the addition of a flag that prevents getting a few extra calls to your motion handler even when the first line in that handler is motionManager.stopDeviceMotionUpdates().
Also, a value of around 3.0 can be useful if you want to ignore the shake, but detect a bump. I found 0.3 to be way too low as it ended up being more like "detect move". In my tests, the ranges were more like:
0.75 - 2.49 is a better range for shake sensitivity
2.5 - 5.0 is a good range for "ignore shake, detect bump"
Here is the complete view controller for an Xcode single VC template:
import UIKit
import CoreMotion
class ViewController: UIViewController {
lazy var motionManager: CMMotionManager = {
return CMMotionManager()
}()
let accelerationThreshold = 3.0
var handlingShake = false
override func viewWillAppear(animated: Bool) {
handlingShake = false
motionManager.startDeviceMotionUpdatesToQueue(NSOperationQueue.currentQueue()!) { [weak self] (motion, error) in
if
let userAcceleration = motion?.userAcceleration,
let _self = self {
print("\(userAcceleration.x) / \(userAcceleration.y)")
if (fabs(userAcceleration.x) > _self.accelerationThreshold
|| fabs(userAcceleration.y) > _self.accelerationThreshold
|| fabs(userAcceleration.z) > _self.accelerationThreshold)
{
if !_self.handlingShake {
_self.handlingShake = true
_self.handleShake();
}
}
} else {
print("Motion error: \(error)")
}
}
}
override func viewWillDisappear(animated: Bool) {
// or wherever appropriate
motionManager.stopDeviceMotionUpdates()
}
func handleShake() {
performSegueWithIdentifier("showShakeScreen", sender: nil)
}
}
And the storyboard I used for this test looks like this:
It's also worth noting that CoreMotion is not testable in the simulator. Because of this constraint you may still find it worthwhile to additionally implement the UIDevice method of detecting motion shake. This would allow you to manually test shake in the simulator or give UITests access to shake for testing or tools like fastlane's snapshot. Something like:
class ViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
if TARGET_OS_SIMULATOR != 0 {
if event?.subtype == .MotionShake {
// do stuff
}
}
}
}
And then use Ctrl-Cmd-Z to test shake in the simulator.
Use core motion.
Link your binary with the CoreMotion framework.
Include
#import <CoreMotion/CoreMotion.h>
in your class.
Create an instance of CMMotionManager.
Set the deviceMotionUpdateInterval property to a suitable value.
Then call startDeviceMotionUpdatesToQueue.
You will get continuous updates inside the block, which include acceleration, magnetic field, rotation, etc.
You will get the data you require.
One thing to be taken care of is that the update shall be so rapid if
the interval is too small, and hence you will have to employ suitable
logic to handle the same.
Heres how I did this using Swift 3.
Import CoreMotion and create an instance
import CoreMotion
let motionManager = CMMotionManager()
On ViewDidLoad or wherever you want to start checking for updates:
motionManager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler:{
deviceManager, error in
if(error == nil){
if let mgr = deviceManager{
self.handleMotion(rate: mgr.rotationRate)
}
}
})
This function takes the rotation rate and gets a sum for the absolute values for x,y and z movements
func handleMotion(rate: CMRotationRate){
let totalRotation = abs(rate.x) + abs(rate.y) + abs(rate.z)
if(totalRotation > 20) {//Play around with the number 20 to find the optimal level for your case
start()
}else{
print(totalRotation)
}
}
func start(){
//The function you want to trigger when the device is rotated
}

Resources