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.
Related
I am trying to add(move forward) 10 second song duration or minus(move backward) 10 second in Spotify player but i am really confused how to add or minus.
When i m trying to use this code the song is not changed duration
// forward button action
#IBAction func moveFrdBtnAction(_ sender: Any) {
SpotifyManager.shared.audioStreaming(SpotifyManager.shared.player, didSeekToPosition: TimeInterval(10))
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
player?.seek(to: position, callback: { (error) in
let songDuration = audioStreaming.metadata.currentTrack?.duration as Any as! Double
self.delegate?.getSongTime(timeCount: Int(songDuration)+1)
})
}
We are making a music application using the same SDK in both the platforms (Android & iOS), the seekToPosition method of the Spotify SDK is working correctly in the Android version, however, it is not working in the iOS one.The delegate method calls itself but the music stops.
Can you kindly let us know why this scenario is happening, and what should we do to run it on the iOS devices as well.
Can someone please explain to me how to solve this , i've tried to solve this but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
I don't use this API so my answer will be based your code and Spotify's reference documentation.
I think there are a few things wrong with your flow:
As Robert Dresler commented, you should (approximately) never call a delegate directly, a delegate calls you.
I'm pretty sure your action currently results in jumping to exactly 10 seconds, not by 10 seconds.
(As an aside, I'd suggest changing the name of your function moveFrdBtnAction to at least add more vowels)
Anyway, here's my best guess at what you want:
// forward button action
#IBAction func moveForwardButtonAction(_ sender: Any) {
skipAudio(by: 10)
}
#IBAction func moveBackButtonAction(_ sender: Any) {
skipAudio(by: -10)
}
func skipAudio(by interval: TimeInterval) {
if let player = player {
let position = player.playbackState.position // The documentation alludes to milliseconds but examples don't.
player.seek(to: position + interval, callback: { (error) in
// Handle the error (if any)
})
}
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
// Update your UI
}
Note that I have not handled seeking before the start of the track, nor after the end which could happen with a simple position + interval. The API may handle this for you, or not.
You could take a look at the examples here: spotify/ios-sdk. In the NowPlayingView example they use the 'seekForward15Seconds', maybe you could use that? If you still need 10s I have added a function below. The position is in milliseconds.
"position: The position to seek to in milliseconds"
docs
ViewController.swift
var appRemote: SPTAppRemote {
get {
return AppDelegate.sharedInstance.appRemote
}
}
fileprivate func seekForward15Seconds() {
appRemote.playerAPI?.seekForward15Seconds(defaultCallback)
}
fileprivate seekBackward15Seconds() {
appRemote.playerAPI?.seekBackward15Seconds(defaultCallback)
}
// TODO: Or you could try this function
func seekForward(seconds: Int){
appRemote.playerAPI?.getPlayerState({ (result, error) in
// playback position in milliseconds
let current_position = self.playerState?.playbackPosition
let seconds_in_milliseconds = seconds * 1000
self.appRemote.playerAPI?.seek(toPosition: current_position + seconds_in_milliseconds, callback: { (result, error) in
guard error == nil else {
print(error)
return
}
})
})
}
var defaultCallback: SPTAppRemoteCallback {
get {
return {[weak self] _, error in
if let error = error {
self?.displayError(error as NSError)
}
}
}
}
AppDelegate.swift
lazy var appRemote: SPTAppRemote = {
let configuration = SPTConfiguration(clientID: self.clientIdentifier, redirectURL: self.redirectUri)
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.connectionParameters.accessToken = self.accessToken
appRemote.delegate = self
return appRemote
}()
class var sharedInstance: AppDelegate {
get {
return UIApplication.shared.delegate as! AppDelegate
}
}
Edit1:
For this to work you need to follow the Prepare Your Environment:
Add the SpotifyiOS.framework to your Xcode project
Hope it helps!
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 ( + & - )
I have notification badges for two of my tabs, at the 1 position and the 2 position of the UITabBarController. The badges for the number 1 position show fine and even if the app is open it will show the new badge number. However the 2 position tab shows once, and when the app is open or even closed it won't show the new badge number. It only will show the badge number after hours of inactivity. Would this be an issue with my code or a problem on my backend? The 1 position is request notifications and 2 position is chat messages.
class NewTabBarViewController: UITabBarController {
var numPendingRequests = 0
var numUnseenConversations = 0
override func viewDidLoad() {
super.viewDidLoad()
observeNumPendingRequests()
observeNumUnseenConversations()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
sendFCMToken()
observeNumPendingRequests()
observeNumUnseenConversations()
AuthService().getCurrentUserInfo()
// Show notification center if the app launched from a notification
// Typically from the background or killed app state
if NotificationService.launchedFromNotification {
showNotificationCenter()
NotificationService.launchedFromNotification = false
}
// Observe if the app is opened from a notification
// Typically from the foreground state
NotificationCenter.default.addObserver(self, selector: #selector(showNotificationCenter), name: NotificationService.didLaunchFromNotification, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Stop observing if the app is opened from a notification
NotificationCenter.default.removeObserver(self, name: NotificationService.didLaunchFromNotification, object: nil)
}
// Select the tab item with the notification center
#objc func showNotificationCenter() {
self.selectedIndex = 2
}
/**
Sends the users private Firebase Cloud Messaging Token to the database
to be used by the Cloud Functions for sending Push Notifications
*/
func sendFCMToken() {
guard let token = Messaging.messaging().fcmToken, let user = Auth.auth().currentUser else { return }
let db = Database.database().reference()
let ref = db.child("FCMToken/\(user.uid)")
ref.setValue(token)
}
/**
Observes the number of received requests that are PENDING
and updates the App Badge Icon Number and tabBar Item accordingly
*/
func observeNumPendingRequests() {
guard let user = Auth.auth().currentUser else { return }
let db = Database.database().reference()
let ref = db.child("receivedRequests/\(user.uid)").queryOrderedByValue().queryEqual(toValue: "PENDING")
ref.observe(.value, with: { snapshot in
self.numPendingRequests = Int(snapshot.childrenCount)
self.setApplicationBadgeCount()
})
}
func observeNumUnseenConversations() {
guard let user = Auth.auth().currentUser else { return }
let db = Database.database().reference()
let ref = db.child("conversations/users/\(user.uid)").queryOrdered(byChild: "seen").queryEqual(toValue: false)
ref.observe(.value, with: { snapshot in
self.numUnseenConversations = Int(snapshot.childrenCount)
self.setApplicationBadgeCount()
})
}
func setApplicationBadgeCount() {
let total = numUnseenConversations + numPendingRequests
UIApplication.shared.applicationIconBadgeNumber = Int(total)
self.tabBar.items?[1].badgeValue = numPendingRequests > 0 ? "\(numPendingRequests)" : nil
self.tabBar.items?[2].badgeValue = numUnseenConversations > 0 ? "\(numUnseenConversations)" : nil
}
}
Is there actually a number of unseen conversations being returned? If it's nil, then there will not be a badge. Double check that, and even try setting it to a hard-coded string for a quick and dirty test. Also, are you on tab 1 when trying to see the updated badge on tab 2? Try changing the tab 2 badge when you have that tab selected. You might also want to make sure that self.tabBar.items?[2] is not nil.
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
I'm trying to detect when the entire left side of an iPhone is bumped. Here's what I have so far:
let manager = CMMotionManager()
override func viewDidLoad() {
super.viewDidLoad()
if manager.deviceMotionAvailable {
manager.deviceMotionUpdateInterval = 0.02
manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) {
[weak self] (data: CMDeviceMotion!, error: NSError!) in
if data.userAcceleration.x > 2 {
// Perform action
}
}
}
}
The code above works as long as I bump the top left corner of the phone. However, if I try to bump the entire phone directly to the left in a straight line, it rarely if ever works. Is there something I'm missing here?
Thanks!
if data.userAcceleration.x > 2 {
// Perform action
}
Make 2, say 1.5 or smaller, because 2 requires a really fast acceleration.