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

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.

Related

How Can I Add DeviceMotion Capabilities to a Swift Playground?

I am working on a Swift playground and I am trying to use this code to get the device motion.
#objc func update()
{
if let deviceMotion = motionManager.deviceMotion {
print("Device Motion Yaw: \(deviceMotion.attitude.yaw)")
}
}
However, it seems that device motion does not work on a Swift playground even though it works in iOS. How would I change a playground to support device motion? I am using an iPad running iOS 12 and the latest version of Swift Playgrounds and a Mac for the code. I know that the method gets called perfectly, and the code runs perfectly when I put it as part of an iOS app on both an iPad and an iPhone. How would I modify a playground to support this, as from my understanding it does not by default?
It is entirely possible. I’ve done it on several occasions. You’ll need a CMMotionManager class. There are many ways to do this, but I would recommend using a timer. Here is some example code, taken from Apple’s developer documentation and modified to fit the question.
let motion = CMMotionManager()
func startDeviceMotion() {
if motion.isDeviceMotionAvailable {
//How often to push updates
self.motion.deviceMotionUpdateInterval = 1.0/60.0
self.motion.showsDeviceMovementDisplay = true
self.motion.startDeviceMotionUpdates(using: .xMagneticNorthZVertical)
// Configure a timer to fetch the motion data.
self.timer = Timer(fire: Date(), interval: (1.0 / 60.0), repeats: true,
block: { (timer) in
if let data = self.motion.deviceMotion {
let x = data.attitude.pitch
let y = data.attitude.roll
let z = data.attitude.yaw
//Use the data
}
})
RunLoop.current.add(self.timer!, forMode: RunLoop.Mode.default)
}
}
startDeviceMotionUpdates()
Either do that or try something like this, also from the documentation
func startQueuedUpdates() {
if motion.isDeviceMotionAvailable { self.motion.deviceMotionUpdateInterval = 1.0 / 60.0
self.motion.showsDeviceMovementDisplay = true
self.motion.startDeviceMotionUpdates(using: .xMagneticNorthZVertical,
to: self.queue, withHandler: { (data, error) in
// Make sure the data is valid before accessing it.
if let validData = data {
// Get the attitude relative to the magnetic north reference frame.
let roll = validData.attitude.roll
let pitch = validData.attitude.pitch
let yaw = validData.attitude.yaw
// Use the motion data in your app.
}
})
}
}

app crashed when 'startDeviceMotionUpdatesToQueue' of CMMotionManager callback

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 ( + & - )

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 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 make my iphone vibrate twice when I click on a button?

I search to make vibrate twice my iphone when I click on a button (like a sms alert vibration)
With AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
I obtain just one normal vibration but I want two shorts :/.
Update for iOS 10
With iOS 10, there are a few new ways to do this with minimal code.
Method 1 - UIImpactFeedbackGenerator:
let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
feedbackGenerator.impactOccurred()
Method 2 - UINotificationFeedbackGenerator:
let feedbackGenerator = UINotificationFeedbackGenerator()
feedbackGenerator.notificationOccurred(.error)
Method 3 - UISelectionFeedbackGenerator:
let feedbackGenerator = UISelectionFeedbackGenerator()
feedbackGenerator.selectionChanged()
#import <AudioToolbox/AudioServices.h>
AudioServicesPlayAlertSound(UInt32(kSystemSoundID_Vibrate))
This is the swift function...See this article for detailed description.
This is what I came up with:
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var counter = 0
var timer : NSTimer?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func vibratePhone() {
counter++
switch counter {
case 1, 2:
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
default:
timer?.invalidate()
}
}
#IBAction func vibrate(sender: UIButton) {
counter = 0
timer = NSTimer.scheduledTimerWithTimeInterval(0.6, target: self, selector: "vibratePhone", userInfo: nil, repeats: true)
}
}
When you press the button, the timer starts and repeats at desired time interval. The NSTimer calls the vibratePhone(Void) function and from there I can control how many times the phone will vibrate. I used a switch in this case, but you could use a if else, too. Simply set a counter to count each time the function is called.
If you want to vibrate only two times. You can just..
func vibrate() {
AudioServicesPlaySystemSoundWithCompletion(kSystemSoundID_Vibrate) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
}
}
And vibrating multiple times can be possible by using recursion and AudioServicesPlaySystemSoundWithCompletion.
You can pass a count number to vibrate function like vibrate(count: 10). Then it vibrates 10 times.
func vibrate(count: Int) {
if count == 0 {
return
}
AudioServicesPlaySystemSoundWithCompletion(kSystemSoundID_Vibrate) { [weak self] in
self?.vibrate(count: count - 1)
}
}
In case of using UIFeedbackGenerator, There is a great library Haptica
Hope it helps.

Resources