I'm trying to change button state depending on call state.
I used code from here to detect call state: How to get a call event using CTCallCenter:setCallEventHandler: that occurred while the app was suspended?
And it works fine when app is in foreground. But it doesn't work in background at all. In documentation for CTCallCenter.callEventHandler:
When your application resumes the active state, it receives a single
call event for each call that changed state—no matter how many state
changes the call experienced while your application was suspended. The
single call event sent to your handler, upon your application
returning to the active state, describes the call’s state at that
time.
But I don't get any call events when app resumes active. All I get is last saved call state when app was in foreground. How can I detect call state in background?
Here is my code:
AppDelegate.swift
let callСenter = CTCallCenter()
func block (call:CTCall!)
{
callState = String(call.callState)
print(call.callState)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
//check for call state
callСenter.callEventHandler = block
...
return true
}
ViewController.swift
override func viewDidLoad()
{
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(cameBackFromSleep),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil
)
...
}
func cameBackFromSleep()
{
self.viewWillAppear(true)
}
override func viewWillAppear(_ animated: Bool)
{
switch callState
{
case "CTCallStateConnected":
print("callState: ", callState)
self.textLabel.isHidden = true
startBtnAnimation()
case "CTCallStateDisconnected":
print("callState: ", callState)
self.textLabel.center.y += self.view.bounds.height
self.textLabel.isHidden = false
stopBtnAnimation()
default: break
}
}
Finally, I solved it! I used code from this answer: Find if user is in a call or not?
I removed everything from AppDelegate, all job is done in ViewController:
override func viewDidLoad()
{
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(cameBackFromSleep),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil
)
...
}
private func isOnPhoneCall() -> Bool
{
let callCntr = CTCallCenter()
if let calls = callCntr.currentCalls
{
for call in calls
{
if call.callState == CTCallStateConnected || call.callState == CTCallStateDialing || call.callState == CTCallStateIncoming
{
print("In call")
return true
}
}
}
print("No calls")
return false
}
func cameBackFromSleep()
{
self.viewWillAppear(true)
}
override func viewWillAppear(_ animated: Bool)
{
print("is on call", isOnPhoneCall())
switch isOnPhoneCall()
{
case true:
print("startBtnAnimation")
startBtnAnimation()
recordBtnIsPressed = true
case false:
print("stopBtnAnimation")
stopBtnAnimation()
recordBtnIsPressed = false
default: break
}
}
Now it works fine. Not sure why CTCallCenter works so weird in AppDelegate.
Related
I want to check if both the volume buttons are working fine. So I set the observer AVSystemController_SystemVolumeDidChangeNotification to check that.
NotificationCenter.default.addObserver(self, selector: #selector(volumeCallback(notification:)), name: NSNotification.Name("AVSystemController_SystemVolumeDidChangeNotification"), object: nil)
Given is volumeCallback method:
#objc private func volumeCallback(notification: NSNotification) {
// check if app is in forground
guard UIApplication.shared.applicationState == .active else {
return
}
//get volume level
if let userInfo = notification.userInfo {
if let volumeChangeType = userInfo["AVSystemController_AudioVolumeChangeReasonNotificationParameter"] as? String {
if volumeChangeType == "ExplicitVolumeChange" {
print("value changed")
let level = userInfo["AVSystemController_AudioVolumeNotificationParameter"] as? Float
guard let volLevel = level else {
return
}
// my work here
}
}
}
}
Now the problem is, I am not getting callback in volumeCallback for the first installation of the app. The weird thing is, this method is being called when the app is in background, but not being called in foreground.
I am using iPhone 5s (iOS 10.3.3).
I don't understand what is the problem in this code. Any help will be appreciated.
This can be easily done with key-value observer as AVAudioSession provides outputVolume property. Check here.
You can just add observer on this property and get callbacks.
Here's a simple way of doing this in Swift 5:
// Audio session object
private let session = AVAudioSession.sharedInstance()
// Observer
private var progressObserver: NSKeyValueObservation!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
do {
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("cannot activate session")
}
progressObserver = session.observe(\.outputVolume) { [weak self] (session, value) in
print(session.outputVolume)
}
return true
}
I want to do an action after the user tapped on the call button and made a call then returned to the app.
This is my function for making a phone call :
let phoneURL = URL(string: String(format: "tel://%#", phoneNumber.englishNumbers))
UIApplication.shared.open(phoneURL!)
and I have set an observer on CallView in viewDidLoad() like this:
NotificationCenter.default.addObserver(self, selector: #selector (showFeedBack), name: UIApplication.didEnterBackgroundNotification, object: nil)
After I made the call and pressed on the End button (the red button which ends the call). the CallView would appear but the notification won't get called.
Am I using the right notification? or is this the correct approach for detecting when a user made a phone call through your app and came back?
P.S. I have used willResignActiveNotification notification. but it send the notification before even making the call (when the alert is appeared and the user has not select anything yet)
You can use CXCallObserver to listen the call events as below,
import CallKit
class ViewController: UIViewController, CXCallObserverDelegate {
let co = CXCallObserver()
override func viewDidLoad() {
super.viewDidLoad()
co.setDelegate(self, queue: DispatchQueue.main)
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasEnded {
print("Call is ended")
}
if call.hasEnded == false && call.hasConnected {
print("Call is connected")
}
if call.isOutgoing {
print("This is an outgoing call")
} else if call.hasEnded == false && call.hasConnected == false {
print("This is an incoming call")
}
}
}
The Problem
I wanted to create a quick action (3D Touch) that allows a user to go directly to the statistics section of a game. I based my solution on the code from this tutorial and got it working with this code. I added some extra code because I want to use the quick action to do something different than in the tutorial. This is the function that is responsible for performing a segue from the initial view to the statistics view. It's in AppDelegate.swift:
From AppDelegate.swift
func handleQuickAction(shortcutItem: UIApplicationShortcutItem) -> Bool {
var quickActionHandled = false
let type = shortcutItem.type.components(separatedBy: ".").last
if let shortcutType = Shortcut.init(rawValue: type!) {
switch shortcutType {
case .statistics:
quickActionHandled = true
// I use dispatchQueue to ensure that the segue occurs immediately
DispatchQueue.main.async {
self.window?.rootViewController?.performSegue(withIdentifier: "ARView_to_stats", sender: self.window?.rootViewController)
}
}
}
return quickActionHandled
}
This function is called from the following delegate function:
From AppDelegate.swift
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
completionHandler(handleQuickAction(shortcutItem: shortcutItem))
}
This setup works perfectly if the app has been closed (from the app switcher / multitasking view) and I tap on the quick action. However, if I go home without closing the application completely and try to do the same, it does not go to the statistics view but rather to the game view.
I've tried this many times over and it crashes once in a while with the following message:
com.apple.CoreMotion.MotionThread (11): EXC_BAD_ACCESS (code=1, address=0x48307beb8)
How could I change this to make it work as desired.
What I tried
Removing DispatchQueue.main.async
Try something like this
var isQuickLaunched = false
var quickLaunchOption: Shortcut!
func handleQuickAction(shortcutItem: UIApplicationShortcutItem) -> Bool {
var quickActionHandled = false
let type = shortcutItem.type.components(separatedBy: ".").last
if let shortcutType = Shortcut.init(rawValue: type!) {
isQuickLaunched = true
quickLaunchOption = shortcutType
quickActionHandled = true
}
return quickActionHandled
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
completionHandler(handleQuickAction(shortcutItem: shortcutItem))
}
func applicationDidBecomeActive(application: UIApplication) {
if (isQuickLaunched == true) {
isQuickLaunched = false
switch quickLaunchOption {
case .statistics:
DispatchQueue.main.async {
self.window?.rootViewController?.performSegue(withIdentifier: "ARView_to_stats", sender: self.window?.rootViewController)
}
}
}
}
I hosted Ejabberd chat server on Amazon and added two user there, when I tried to connect Ejabberd server through Adium, it asks for certificate then only it gets connected, now I'm developing chat application in Swift using Ejabberd server and XMPP, I configured all code , passed host name and port no: 5222, but its not connecting to server.
Should I need to write program to fetch server certificate and pass my computer certificate (.p12) file to server?
NOTE: I configured Ejabberd server in localhost and through iOS Swift Code, it's working perfectly when I send message from iOS app then it shows in Adium and when Adium user send message then I go to Ejabberd Web Admin Panel and can check offline Messages.
Here is the code in Swift for connecting Ejabberd hosted on Amazon:
//
// AppDelegate.swift
// Thanks to Process One for this.
import UIKit
import XMPPFramework
protocol ChatDelegate {
func buddyWentOnline(_ name: String)
func buddyWentOffline(_ name: String)
func didDisconnect()
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, XMPPRosterDelegate, XMPPStreamDelegate {
var window: UIWindow?
var delegate:ChatDelegate! = nil
let xmppStream = XMPPStream()
let xmppRosterStorage = XMPPRosterCoreDataStorage()
var xmppRoster: XMPPRoster
override init() {
xmppRoster = XMPPRoster(rosterStorage: xmppRosterStorage)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
DDLog.add(DDTTYLogger.sharedInstance())
setupStream()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
disconnect()
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
connect()
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
//MARK: Private Methods
func setupStream() {//fileprivate
//xmppRoster = XMPPRoster(rosterStorage: xmppRosterStorage)
xmppRoster.activate(xmppStream)
xmppStream?.addDelegate(self, delegateQueue: DispatchQueue.main)
xmppRoster.addDelegate(self, delegateQueue: DispatchQueue.main)
}
func goOnline() { //fileprivate
let presence = XMPPPresence()
let domain = xmppStream?.myJID.domain
if domain == "gmail.com" || domain == "gtalk.com" || domain == "talk.google.com" {
let priority = DDXMLElement.element(withName: "priority", stringValue: "24") as! DDXMLElement
presence?.addChild(priority)
}
xmppStream?.send(presence)
}
func goOffline() { //fileprivate
let presence = XMPPPresence(type: "unavailable")
xmppStream?.send(presence)
}
func connect() -> Bool {
xmppStream.hostName = "amazon hosted ejabber ip address"
xmppStream.hostPort=5222
if !(xmppStream?.isConnected())! {
let jabberID = UserDefaults.standard.string(forKey: "userID")
let myPassword = UserDefaults.standard.string(forKey: "userPassword")
if !(xmppStream?.isDisconnected())! {
return true
}
if jabberID == nil && myPassword == nil {
return false
}
xmppStream?.myJID = XMPPJID.init(string: jabberID)
do {
try xmppStream?.connect(withTimeout: XMPPStreamTimeoutNone)
print("Connection success")
return true
} catch {
print("Something went wrong!")
return false
}
} else {
return true
}
}
func disconnect() {
goOffline()
xmppStream?.disconnect()
}
//MARK: XMPP Delegates
func xmppStreamDidConnect(_ sender: XMPPStream!) {
do {
try xmppStream?.authenticate(withPassword: UserDefaults.standard.string(forKey: "userPassword"))
} catch {
print("Could not authenticate")
}
}
func xmppStreamDidAuthenticate(_ sender: XMPPStream!) {
goOnline()
}
func xmppStream(_ sender: XMPPStream!, didReceive iq: XMPPIQ!) -> Bool {
print("Did receive IQ")
return false
}
func xmppStream(_ sender: XMPPStream!, didReceive message: XMPPMessage!) {
print("Did receive message \(message)")
}
func xmppStream(_ sender: XMPPStream!, didSend message: XMPPMessage!) {
print("Did send message \(message)")
}
func xmppStream(_ sender: XMPPStream!, didReceive presence: XMPPPresence!) {
let presenceType = presence.type()
let myUsername = sender.myJID.user
let presenceFromUser = presence.from().user
if presenceFromUser != myUsername {
print("Did receive presence from \(presenceFromUser)")
if presenceType == "available" {
delegate.buddyWentOnline("\(presenceFromUser)#gmail.com")
} else if presenceType == "unavailable" {
delegate.buddyWentOffline("\(presenceFromUser)#gmail.com")
}
}
}
func xmppRoster(_ sender: XMPPRoster!, didReceiveRosterItem item: DDXMLElement!) {
print("Did receive Roster item")
}
}
Finally i managed to solve this issue and i am really amaze to implement chat application using Ejabberd server hosted on Amazon.
Issue :
1. When i was running my application , it was not connecting ?
Answer : Certificate issue (Server - client side certificate handshake resolves issue)
2. Unable to fetch online buddy ?
Answer : i used Adium to make all user online and then run my application and it works like a charm.
Great learning for me for this application.
If any1 having any issue with chat ,feel free to comment i am looking forward to help. Thanks
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
println("Something cool")
}
}
It's ok for the Simulator ,I will get continuous "Something cool" through I tapped the home button. But it worked out when I debug the app with my iPhone. I did't get anything when I tapped the home button and make my app run background.
Someone told me I can play a long blank music to make my App run in the background.But I don't know how to play music in the background with swift #matt
You can use beginBackgroundTaskWithExpirationHandler to get some background execution time.
class ViewController: UIViewController {
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
override func viewDidLoad() {
super.viewDidLoad()
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTaskIdentifier!)
})
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update", userInfo: nil, repeats: true)
}
func update() {
println("Something cool")
}
}
Swift 3.0
backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
})
This code was inspired by this answer, but I ported to swift.
This apparently only runs for 3 minutes on iOS 7+.
I'm willing to bet that you don't need to run a timer in the background, what you need is to know the time that has elapsed while you were suspended. That is a much better use of your resources than trying to play an empty sound file.
Use applicationWillResignActive and applicationDidBecomeActive to determine how much time has elapsed. First save the fireDate of your timer and invalidate it in applicationWillResignActive
func applicationWillResignActive(application: UIApplication) {
guard let t = self.timer else { return }
nextFireDate = t.fireDate
t.invalidate()
}
Next, determine how much time is left for the timer in applicationDidBecomeActive by comparing the time now to the fireDate you saved in the applicationWillResignActive call:
func applicationDidBecomeActive(application: UIApplication) {
guard let n = nextFireDate else { return }
let howMuchLonger = n.timeIntervalSinceDate(NSDate())
if howMuchLonger < 0 {
print("Should have already fired \(howMuchLonger) seconds ago")
target!.performSelector(selector!)
} else {
print("should fire in \(howMuchLonger) seconds")
NSTimer.scheduledTimerWithTimeInterval(howMuchLonger, target: target!, selector: selector!, userInfo: nil, repeats: false)
}
}
If you need a repeating one, just do math to figure out how many times the timer should have fired while in the background
let howManyTimes = abs(howMuchLonger) / repeatInterval
when I tapped the home button and make my app run background
No, that's a false assumption. When you tap the home button, you do not make your app run in the background. You make your app suspend in the background. Your code does not run when you are in the background.
Apps can get special permission to run in the background just in order to perform certain limited activities, like continuing to play music or continuing to use Core Location. But your app does none of those things, so it goes into the background and stops.
add this code in appdelegate for run a task in background , its working finely,
var backgroundUpdateTask: UIBackgroundTaskIdentifier = 0
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
return true
}
func applicationWillResignActive(application: UIApplication) {
self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func applicationWillEnterForeground(application: UIApplication) {
self.endBackgroundUpdateTask()
}