how to send the locations to the server continuously for 5 hours when the application is in background mode ? - background

I am making an app in which I have to send the locations to the server on an Api call . It can work in the background also . I have implemented the code for this , but when I came back from background . My current work is not resuming . It is going just to the dashboard page , not backing to the page which is in the background .
Here is my code in the appdelegate
func applicationWillEnterForeground(_ application: UIApplication) {
if !isComingFromTrip {
locationManager.stopUpdatingLocation()
}
locationStarted = false
}
func applicationDidEnterBackground(_ application: UIApplication) {
if isComingFromTrip {
if UIApplication.shared.applicationState == .background {
print("start backgroun tracking from appdelegate")
locationManager.startUpdatingLocation()
}
//change locationManager status after time
self.runBackgroundTask(20)
} else {
isComingFromTrip = false
}
FIRMessaging.messaging().disconnect()
print("Disconnected from FCM.")
}
func runBackgroundTask(_ time: Int) -> Void {
if UIApplication.shared.applicationState == .background {
//create UIBackgroundTaskIdentifier and create tackground task, which starts after time
backgroundUpdateTask = app.beginBackgroundTask(expirationHandler: {() -> Void in
self.app.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
})
DispatchQueue.global(qos: .default).async(execute: {() -> Void in
var t = Timer.scheduledTimer(timeInterval: TimeInterval(time), target: self, selector: #selector(self.startTrackingBg), userInfo: nil, repeats: false)
RunLoop.current.add(t, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
})
}
}
func startTrackingBg() {
//write background time remaining
print(String(format: "backgroundTimeRemaining: %.0f", UIApplication.shared.backgroundTimeRemaining))
//set default time
var time: Int = 60
//if locationManager is ON
if locationStarted == true {
//stop update location
locationManager.stopUpdatingLocation()
locationStarted = false
}
else {
//start updating location
locationManager.startUpdatingLocation()
locationStarted = true
//Time how long the application will update your location
time = 5
}
self.runBackgroundTask(time)
}
func StartupdateLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
}
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
}
I have enabled the background mode also Target->Capabilities-> Location
and also added key for this .

Related

CallKit + WebRTC: CallKit call is getting disconnected when pressing lock / Power button in iOS

It is a conferencing app and I am initiating outgoing call to make my VoIP call as high priority and doesn't interrupt the incoming call when i am in VoIP call.
I am using WebRTC + CallKit in my App.
I started a call and when I press Lock / Power button then the CallKit call is getting disconnected and the My Voip call audio route changes to Receiver and remains.
Why locking the iPhone terminating the call.
Here is my Code.
var callUUID: UUID?
extension AppDelegate {
func initiateCallKitCall() {
let config = CXProviderConfiguration(localizedName: "AppName")
config.includesCallsInRecents = false;
config.supportsVideo = true;
config.maximumCallsPerCallGroup = 1
provider = CXProvider(configuration: config)
guard let provider = provider else { return }
provider.setDelegate(self, queue: nil)
callController = CXCallController()
guard let callController = callController else { return }
callUUID = UUID()
let transaction = CXTransaction(action: CXStartCallAction(call: callUUID!, handle: CXHandle(type: .generic, value: "AppName")))
callController.request(transaction, completion: { error in
print("Error is : \(String(describing: error))")
})
}
func endCallKitCall(userEnded: Bool) {
self.userEnded = userEnded
guard provider != nil else { return }
guard let callController = callController else { return }
if let uuid = callUUID {
let endCallAction = CXEndCallAction(call: uuid)
callController.request(
CXTransaction(action: endCallAction),
completion: { error in
if let error = error {
print("Error: \(error)")
} else {
print("Success")
}
})
}
}
func isCallGoing() -> Bool {
let callController = CXCallController()
if callController.callObserver.calls.count != 0 {
return true
}
return false
}
}
extension AppDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("-Provider-providerDidReset")
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("-Provider-perform action: CXAnswerCallAction")
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill()
print("-Provider: End Call")
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
action.fulfill()
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 3) {
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: Date())
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date())
}
}
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
action.fulfill()
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
}
The power button ends a call if and only if the call is running through the built-in speaker on top of the screen (receiver). In any other case (i.e. the audio is playing through headphones, Bluetooth or built-in loudspeaker) the power button will not end the call.
The same is true with the native phone calls.
I would like to answer all issues related to CallKit here.
Answer for my question is:
You need to set the AudioSession mode to .default after your voip call established successfully.
try AVAudioSession.sharedInstance().setMode(.default)
AudioRouteManager.shared.fourceRouteAudioToSpeakers()

How to add snooze effect once a notification is delivered in ios 10

I am implementing UserNotification in my app. When the notification gets fired it shows two action, in one i want to add snooze effect, it must snooze after 5 mins again. How to handle it ? thanks for all ! help if any one do have idea
Well to snooze notification you can create another notification with same details of current notification and increase the fire date by 5 mins.
Here is the code I used :
func snoozeScheduledNotification(notification:UILocalNotification) -> Void {
// Snooze for 10 mins
let localNotification = UILocalNotification()
localNotification.fireDate = notification.fireDate?.addingTimeInterval(60*10)
localNotification.repeatInterval = NSCalendar.Unit(rawValue: 0) // 0 = No Repeat
localNotification.alertBody = notification.alertBody
localNotification.soundName = notification.soundName
localNotification.userInfo = notification.userInfo
localNotification.category = notification.category
UIApplication.shared.scheduleLocalNotification(localNotification)
}
Hope it helps you.
The shortest and simplest code I found about it
For Swift 3/4
extension UNNotification {
func snoozeNotification(for hours: Int, minutes: Int, seconds: Int) {
let content = UNMutableNotificationContent()
content.title = "Another Alert"
content.body = "Your message"
content.sound = .default()
let identifier = self.request.identifier
guard let oldTrigger = self.request.trigger as? UNCalendarNotificationTrigger else {
debugPrint("Cannot reschedule notification without calendar trigger.")
return
}
var components = oldTrigger.dateComponents
components.hour = (components.hour ?? 0) + hours
components.minute = (components.minute ?? 0) + minutes
components.second = (components.second ?? 0) + seconds
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
debugPrint("Rescheduling failed", error.localizedDescription)
} else {
debugPrint("rescheduled success")
}
}
}
}
You just need to call it this way :
response.notification.snoozeNotification(for: 0, minutes: 0, seconds: 30)
Credit goes to Simon Ljungberg : https://gist.github.com/simme/96264d5ceee394083d18e2c64f42a3a9
For iOS10, use this code.
Use this code in AppDelegate.swift file.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
let center = UNUserNotificationCenter.current()
let category = UNNotificationCategory(identifier: "identifier", actions: [], intentIdentifiers: [])
center.setNotificationCategories([category])
center.requestAuthorization(options: [.badge, .alert , .sound]) { (greanted, error) in
print(error)
}
return true
}
You can put this code in any view controller.
let content = UNMutableNotificationContent.init()
content.title = "Notification Title"
content.subtitle = "Notification Sub-Title"
content.body = "Notification Body"
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: "identifier", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
UNUserNotificationCenter.current().delegate = self
if (error != nil){
//handle here
}
}
You can handle notification using following method:
extension UIViewController: UNUserNotificationCenterDelegate {
public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Swift.Void) {
completionHandler( [.alert, .badge, .sound])
}
public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Swift.Void) {
print("Tapped in notification")
}
}
You can use this Blog as reference and Example.

Switch from Built-in Speaker to Bluetooth Speaker

I just found a code that switches the speaker between the built-in and the connected bluetooth speaker:
changeResult = [[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
error:&audioError];
But this is in Obj-C, is there anyone here who is kind enough to convert this to Swift?
Thanks!
UPDATE: Here's the code I am using to record a video. Here's the scenario. I am playing a music while the bluetooth speaker is connected. So the sound output is in the external speaker. When I click the record button, the sound output transfers to the built-in speaker. And that is my problem. The output should always be in the external speaker.
#IBAction func record_video(sender: AnyObject) {
var initialOutputURL = NSURL(fileURLWithPath: "")
do
{
initialOutputURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true).URLByAppendingPathComponent("output").URLByAppendingPathExtension("mov")
}catch
{
print(error)
}
if !isRecording
{
isRecording = true
if let outputs = captureSession.outputs as? [AVCaptureOutput] {
for output in outputs {
captureSession.removeOutput(output)
}
}
do
{
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: [AVAudioSessionCategoryOptions.MixWithOthers, AVAudioSessionCategoryOptions.AllowBluetooth])
//print(audioSession.setOutputDataSource)
}
catch
{
print("Can't Set Audio Session Category: \(error)")
}
do
{
try audioSession.setMode(AVAudioSessionModeVideoRecording)
}
catch
{
print("Can't Set Audio Session Mode: \(error)")
}
// Start Session
do
{
try audioSession.setActive(true)
}
catch
{
print("Can't Start Audio Session: \(error)")
}
UIView.animateWithDuration(0.5, delay: 0.0, options: [.Repeat, .Autoreverse, .AllowUserInteraction], animations: { () -> Void in
self.record.transform = CGAffineTransformMakeScale(0.75, 0.75)
}, completion: nil)
let audioInputDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)
AVCaptureDevice.devicesWithMediaType(AVMediaTypeAudio)
do
{
if let availableInputs = audioSession.availableInputs {
for input in availableInputs {
if input.portType == AVAudioSessionPortBuiltInMic ||
input.portType == AVAudioSessionPortHeadsetMic {
inputs.append(input)
try audioSession.setPreferredInput(input)
}
}
}
let audioInput = try AVCaptureDeviceInput(device: audioInputDevice)
// Add Audio Input
if captureSession.canAddInput(audioInput)
{
captureSession.addInput(audioInput)
}
else
{
NSLog("Can't Add Audio Input")
}
/**
let videoInput: AVCaptureDeviceInput
do
{
videoInput = try AVCaptureDeviceInput(device: captureDevice)
// Add Video Input
if captureSession.canAddInput(videoInput)
{
captureSession.addInput(videoInput)
}
else
{
NSLog("ERROR: Can't add video input")
}
}
catch let error
{
NSLog("ERROR: Getting input device: \(error)")
}
*/
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.Speaker)
videoFileOutput = AVCaptureMovieFileOutput()
captureSession.addOutput(videoFileOutput)
captureSession.sessionPreset = AVCaptureSessionPresetHigh
captureSession.automaticallyConfiguresApplicationAudioSession = false
videoFileOutput?.startRecordingToOutputFileURL(initialOutputURL, recordingDelegate: self)
}
catch let error
{
NSLog("Error Getting Input Device: \(error)")
}
}
else
{
isRecording = false
UIView.animateWithDuration(0.5, delay: 0, options: [], animations: { () -> Void in
self.record.transform = CGAffineTransformMakeScale(1.0, 1.0)
}, completion: nil)
record.layer.removeAllAnimations()
videoFileOutput?.stopRecording()
}
}

TVOS: Race condition at startup

Using the templates and TVML, I start my app with my own loading page, and then call a service to create the main page for the user.
If I initiate the call to the server inside didFinishLaunchingWithOptions, I get the error ITML <Error>: undefined is not an object - undefined - line:undefined:undefined.
From this I assume my asynchronous call to the server is finishing before the javascript App.onLaunch function has completed, and I can only get it to work if I force a wait time before the server is called.
Here is the AppDelegate method:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let appControllerContext = TVApplicationControllerContext()
// our "base" is local
if let jsBootUrl = NSBundle.mainBundle().URLForResource("application", withExtension: "js") {
appControllerContext.javaScriptApplicationURL = jsBootUrl
}
let jsBasePathURL = appControllerContext.javaScriptApplicationURL.URLByDeletingLastPathComponent
baseUrl = jsBasePathURL?.absoluteString
appControllerContext.launchOptions["BASEURL"] = jsBasePathURL?.absoluteString
appController = TVApplicationController(context: appControllerContext, window: window, delegate: self)
// initiate conversation with the server
myPageCreator = PageCreator()
myPageCreator?.delegate = self
myPageCreator?.startDataCall(baseUrl!)
return true
}
Here is the (somewhat boilerplate) javascript function:
App.onLaunch = function(options) {
var javascriptFiles = [
`${options.BASEURL}ResourceLoader.js`,
`${options.BASEURL}Presenter.js`
];
evaluateScripts(javascriptFiles, function(success) {
if (success) {
resourceLoader = new ResourceLoader(options.BASEURL);
var index = resourceLoader.loadResource(`${options.BASEURL}myLoadingPage.xml.js`,
function(resource) {
var doc = Presenter.makeDocument(resource);
doc.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(doc);
});
} else {
/* handle error case here */
}
});
}
Now, if I change the call to the server in the didFinishLaunchingWithOptions, and force it to wait, like this:
...
// race condition hack:
_ = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "testing", userInfo: nil, repeats: false)
return true
}
// initiate conversation with the server
func testing() {
myPageCreator = PageCreator()
myPageCreator?.delegate = self
myPageCreator?.startDataCall(baseUrl!)
}
.. it will work. But I don't like that solution! What can I do to stop this race condition from happening?
You need a way for Javascript to communicate with Swift so that you know when App.onLaunch has finished running it's scripts.
Run this code in your didFinishLaunchingWithOptions method. It will allow you to call onLaunchDidFinishLoading() in Javascript and handle the callback in Swift.
appController.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
let onLaunchDidFinishLoading : #convention(block) () -> Void = {
() -> Void in
//when onLaunchDidFinishLoading() is called in Javascript, the code written here will run.
self.testing()
}
evaluation.setObject(unsafeBitCast(onLaunchDidFinishLoading, AnyObject.self), forKeyedSubscript: "onLaunchDidFinishLoading")
}, completion: {(Bool) -> Void in
})
func testing() {
myPageCreator = PageCreator()
myPageCreator?.delegate = self
myPageCreator?.startDataCall(baseUrl!)
}
Inside App.onLaunch just add onLaunchDidFinishLoading() when the template is done being loaded.
App.onLaunch = function(options) {
var javascriptFiles = [
`${options.BASEURL}ResourceLoader.js`,
`${options.BASEURL}Presenter.js`
];
evaluateScripts(javascriptFiles, function(success) {
if (success) {
resourceLoader = new ResourceLoader(options.BASEURL);
var index = resourceLoader.loadResource(`${options.BASEURL}myLoadingPage.xml.js`,
function(resource) {
var doc = Presenter.makeDocument(resource);
doc.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(doc);
//ADD THE FOLLOWING LINE
onLaunchDidFinishLoading();
});
} else {
/* handle error case here */
}
});
}

CMTimeGetSeconds crash app

I use AVPlayer to play video link and add observer to update progress bar. But with the video which have time more than 10s, my app get crash. Below is my code
Play video button press:
#IBAction func playVideoButtonPressed(sender: AnyObject) {
if let contentUrl = curVideoModel?.video_Content_Url{
player = AVPlayer(URL: NSURL(string: contentUrl))
player?.actionAtItemEnd = AVPlayerActionAtItemEnd.None
playerLayer = AVPlayerLayer(player: player)
playerLayer!.frame = CGRectMake(0, 0, self.imvVideoThumbnail.frame.size.width, self.imvVideoThumbnail.frame.size.height)
playerLayer!.backgroundColor = UIColor.blackColor().CGColor
playerLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
self.playvideoHolderView.layer.addSublayer(playerLayer)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "itemDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: player?.currentItem)
player?.addPeriodicTimeObserverForInterval(CMTimeMakeWithSeconds(1.0/60.0, Int32(NSEC_PER_SEC)), queue: nil, usingBlock: { (time) -> Void in
self.updateProgressbar()
})
player!.play()
}
}
The function call back when video finish play:
NSNotificationCenter.defaultCenter().removeObserver(self, name: AVPlayerItemDidPlayToEndTimeNotification, object: player?.currentItem)
self.progressBar.progress = 0
if notification.name == AVPlayerItemDidPlayToEndTimeNotification{
if player != nil{
player?.pause()
}
if playerLayer != nil{
playerLayer!.removeFromSuperlayer()
}
}
And the function to update progressbar:
var duration: Double
var time: Double
if player != nil{
duration = CMTimeGetSeconds(player!.currentItem.duration)
time = CMTimeGetSeconds(player!.currentTime())
} else{
duration = 0
time = 0
}
self.lblTotalTime.text = "\(Int(duration))"
self.lblCurrentTime.text = "\(Int(time))"
self.progressBar.progress = Float(time / duration)
P/S: It crash on real device(iPod touch iOS 8.3). But play OK on simulator
EDIT: Maybe the comment of Peter is right. I check: if player!.currentItem.duration.value > 0 before get: duration = CMTimeGetSeconds(player!.currentItem.duration) .Then the crash is fixed**