AudioToolBox Recorder gets affect by AVFoundation AudioPlayer - ios

So, I have the following classes:
audio recorder (using AudioToolbox and CoreAudio) that records audio.
audio player (using AVFoundation)
The recorder captures audio, sends it to a server who then replies with another audio, then the player plays the received audio.
When I tried to call the recorder again to record audio, its doesn't records the audio properly.
Any idea on how I can reset the recorder so that it can properly recorder after the audio player finishes playing?
I tried initialize the recorder again (right before recording) but that doesnt work.
the line that affects the recorder is the 2 lines below, which unfortunate are needed in order to play audio with AVFoundation.
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
Flow of execution
Recorder Audio
Send and Received audio to Server
Play Audio from server Recorder Audio at this point the recorder Does NOT record properly
Send and Received audio to Server FAILS
Play Audio FAILS
Thank you in advance!
Lu
Link to recorder project
AudioRecorder:
import UIKit
import CoreAudio
import AudioToolbox
class SpeechRecorder: NSObject {
static let sharedInstance = SpeechRecorder()
// MARK:- properties
#objc enum Status: Int {
case ready
case busy
case error
}
internal struct RecordState {
var format: AudioStreamBasicDescription
var queue: UnsafeMutablePointer<AudioQueueRef?>
var buffers: [AudioQueueBufferRef?]
var file: AudioFileID?
var currentPacket: Int64
var recording: Bool
};
private var _recordState: RecordState?
private var _audioURL:URL?
var format: AudioFormatID {
get { return _recordState!.format.mFormatID }
set { _recordState!.format.mFormatID = newValue }
}
var sampleRate: Float64 {
get { return _recordState!.format.mSampleRate }
set { _recordState!.format.mSampleRate = newValue }
}
var formatFlags: AudioFormatFlags {
get { return _recordState!.format.mFormatFlags }
set { _recordState!.format.mFormatFlags = newValue }
}
var channelsPerFrame: UInt32 {
get { return _recordState!.format.mChannelsPerFrame }
set { _recordState!.format.mChannelsPerFrame = newValue }
}
var bitsPerChannel: UInt32 {
get { return _recordState!.format.mBitsPerChannel }
set { _recordState!.format.mBitsPerChannel = newValue }
}
var framesPerPacket: UInt32 {
get { return _recordState!.format.mFramesPerPacket }
set { _recordState!.format.mFramesPerPacket = newValue }
}
var bytesPerFrame: UInt32 {
get { return _recordState!.format.mBytesPerFrame }
set { _recordState!.format.mBytesPerFrame = newValue }
}
var bytesPerPacket: UInt32 {
get { return _recordState!.format.mBytesPerPacket }
set { _recordState!.format.mBytesPerPacket = newValue }
}
//MARK: - Handlers
public var handler: ((_ status:Status, _ data:NSData?, _ errorDesc:String?) -> Void)?
// MARK:- Init
override init()
{
super.init()
self._recordState = RecordState(format: AudioStreamBasicDescription(),
queue: UnsafeMutablePointer<AudioQueueRef?>.allocate(capacity: 1),
buffers: [AudioQueueBufferRef?](repeating: nil, count: 1),
file: nil,
currentPacket: 0,
recording: false)
}//eom
// MARK:- OutputFile
private func getDocumentsPath()->URL
{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func setOutputFileNameWithDocumentsDirectory(nameDesired:String)
{
_audioURL = getDocumentsPath().appendingPathComponent(nameDesired)
setOutputFile(url: _audioURL!)
}//eom
func setOutputFileNameWithTempDirectory(nameDesired:String)
{
let tempDir = NSTemporaryDirectory()
let tempURLdir = URL(fileURLWithPath: tempDir)
_audioURL = tempURLdir.appendingPathComponent(nameDesired)
setOutputFile(url: _audioURL!)
}//eom
private func setOutputFile(path: String)
{
setOutputFile(url: URL(fileURLWithPath: path))
}//eom
private func setOutputFile(url: URL)
{
AudioFileCreateWithURL(url as CFURL,
kAudioFileWAVEType,
&_recordState!.format,
AudioFileFlags.dontPageAlignAudioData.union(.eraseFile),
&_recordState!.file)
}
// MARK:- Start / Stop Recording
func start()
{
handler?(.busy, nil, nil)
self._recordState?.currentPacket = 0
let inputAudioQueue: AudioQueueInputCallback =
{ (userData: UnsafeMutableRawPointer?,
audioQueue: AudioQueueRef,
bufferQueue: AudioQueueBufferRef,
startTime: UnsafePointer<AudioTimeStamp>,
packets: UInt32,
packetDescription: UnsafePointer<AudioStreamPacketDescription>?) in
let internalRSP = unsafeBitCast(userData, to: UnsafeMutablePointer<RecordState>.self)
if packets > 0
{
var packetsReceived = packets
let outputStream:OSStatus = AudioFileWritePackets(internalRSP.pointee.file!,
false,
bufferQueue.pointee.mAudioDataByteSize,
packetDescription,
internalRSP.pointee.currentPacket,
&packetsReceived,
bufferQueue.pointee.mAudioData)
if outputStream != 0
{
if verbose
{
print("Error with AudioFileWritePackets")
//<----DEBUG
switch outputStream
{
case kAudioFilePermissionsError:
print("kAudioFilePermissionsError")
break
case kAudioFileNotOptimizedError:
print("kAudioFileNotOptimizedError")
break
case kAudioFileInvalidChunkError:
print("kAudioFileInvalidChunkError")
break
case kAudioFileDoesNotAllow64BitDataSizeError:
print("kAudioFileDoesNotAllow64BitDataSizeError")
break
case kAudioFileInvalidPacketOffsetError:
print("kAudioFileInvalidPacketOffsetError")
break
case kAudioFileInvalidFileError:
print("kAudioFileInvalidFileError")
break
case kAudioFileOperationNotSupportedError:
print("kAudioFileOperationNotSupportedError")
break
case kAudioFileNotOpenError:
print("kAudioFileNotOpenError")
break
case kAudioFileEndOfFileError:
print("kAudioFileEndOfFileError")
break
case kAudioFilePositionError:
print("kAudioFilePositionError")
break
case kAudioFileFileNotFoundError:
print("kAudioFileFileNotFoundError")
break
case kAudioFileUnspecifiedError:
print("kAudioFileUnspecifiedError")
break
case kAudioFileUnsupportedFileTypeError:
print("kAudioFileUnsupportedFileTypeError")
break
case kAudioFileUnsupportedDataFormatError:
print("kAudioFileUnsupportedDataFormatError")
break
case kAudioFileUnsupportedPropertyError:
print("kAudioFileUnsupportedPropertyError")
break
case kAudioFileBadPropertySizeError:
print("kAudioFileBadPropertySizeError")
break
default:
print("unknown error")
break
}
//<----DEBUG
}
}
internalRSP.pointee.currentPacket += Int64(packetsReceived)
}
if internalRSP.pointee.recording
{
let outputStream:OSStatus = AudioQueueEnqueueBuffer(audioQueue, bufferQueue, 0, nil)
if outputStream != 0
{
if verbose
{
print("Error with AudioQueueEnqueueBuffer")
//<----DEBUG
switch outputStream
{
case kAudioFilePermissionsError:
print("kAudioFilePermissionsError")
break
case kAudioFileNotOptimizedError:
print("kAudioFileNotOptimizedError")
break
case kAudioFileInvalidChunkError:
print("kAudioFileInvalidChunkError")
break
case kAudioFileDoesNotAllow64BitDataSizeError:
print("kAudioFileDoesNotAllow64BitDataSizeError")
break
case kAudioFileInvalidPacketOffsetError:
print("kAudioFileInvalidPacketOffsetError")
break
case kAudioFileInvalidFileError:
print("kAudioFileInvalidFileError")
break
case kAudioFileOperationNotSupportedError:
print("kAudioFileOperationNotSupportedError")
break
case kAudioFileNotOpenError:
print("kAudioFileNotOpenError")
break
case kAudioFileEndOfFileError:
print("kAudioFileEndOfFileError")
break
case kAudioFilePositionError:
print("kAudioFilePositionError")
break
case kAudioFileFileNotFoundError:
print("kAudioFileFileNotFoundError")
break
case kAudioFileUnspecifiedError:
print("kAudioFileUnspecifiedError")
break
case kAudioFileUnsupportedFileTypeError:
print("kAudioFileUnsupportedFileTypeError")
break
case kAudioFileUnsupportedDataFormatError:
print("kAudioFileUnsupportedDataFormatError")
break
case kAudioFileUnsupportedPropertyError:
print("kAudioFileUnsupportedPropertyError")
break
case kAudioFileBadPropertySizeError:
print("kAudioFileBadPropertySizeError")
break
default:
print("unknown error")
break
//<----DEBUG
}
}
}
}
}
let queueResults = AudioQueueNewInput(&_recordState!.format, inputAudioQueue, &_recordState, nil, nil, 0, _recordState!.queue)
if queueResults == 0
{
let bufferByteSize: Int = calculate(format: _recordState!.format, seconds: 0.5)
for index in (0..<_recordState!.buffers.count)
{
AudioQueueAllocateBuffer(_recordState!.queue.pointee!, UInt32(bufferByteSize), &_recordState!.buffers[index])
AudioQueueEnqueueBuffer(_recordState!.queue.pointee!, _recordState!.buffers[index]!, 0, nil)
}
AudioQueueStart(_recordState!.queue.pointee!, nil)
_recordState?.recording = true
}
else
{
handler?(.error, nil, "Error setting audio input.")
}
}//eom
func stop()
{
_recordState?.recording = false
if let recordingState: RecordState = _recordState
{
AudioQueueStop(recordingState.queue.pointee!, true)
AudioQueueDispose(recordingState.queue.pointee!, true)
AudioFileClose(recordingState.file!)
let audioData:NSData? = NSData(contentsOf: _audioURL!)
handler?(.ready, audioData, nil)
}
}//eom
// MARK:- Helper methods
func calculate(format: AudioStreamBasicDescription, seconds: Double) -> Int
{
let framesRequiredForBufferTime = Int(ceil(seconds * format.mSampleRate))
if framesRequiredForBufferTime > 0
{
return (framesRequiredForBufferTime * Int(format.mBytesPerFrame))
}
else
{
var maximumPacketSize = UInt32(0)
if format.mBytesPerPacket > 0
{
maximumPacketSize = format.mBytesPerPacket
}
else
{
audioQueueProperty(propertyId: kAudioQueueProperty_MaximumOutputPacketSize, value: &maximumPacketSize)
}
var packets = 0
if format.mFramesPerPacket > 0
{
packets = (framesRequiredForBufferTime / Int(format.mFramesPerPacket))
} else
{
packets = framesRequiredForBufferTime
}
if packets == 0
{
packets = 1
}
return (packets * Int(maximumPacketSize))
}
}//eom
func audioQueueProperty<T>(propertyId: AudioQueuePropertyID, value: inout T)
{
let propertySize = UnsafeMutablePointer<UInt32>.allocate(capacity: 1)
propertySize.pointee = UInt32(MemoryLayout<T>.size)
let queueResults = AudioQueueGetProperty(_recordState!.queue.pointee!, propertyId, &value, propertySize)
propertySize.deallocate(capacity: 1)
if queueResults != 0 {
handler?(.error, nil, "Unable to get audio queue property.")
}
}//eom
}
Player:
import UIKit
import AVFoundation
protocol AudioPlayerDelegate {
func audioPlayer_playbackError(playerItemID:String, error:String)
func audioPlayer_playbackSuccess(playerItemID:String)
}
class AudioPlayer: NSObject, AVAudioPlayerDelegate
{
//properties
private var _audioPlayer:AVAudioPlayer?
var delegate:AudioPlayerDelegate?
var playerItemID:String = ""
var volume:Float?
//MARK: - Play Audio
func playAudioFromData(_ playerItemID:String, dataToPlay:Data)
{
do {
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
try sharedSession.setActive(true)
_audioPlayer = try AVAudioPlayer(data: dataToPlay)
_audioPlayer?.numberOfLoops = 0
_audioPlayer?.isMeteringEnabled = true
_audioPlayer?.delegate = self
//volume
if volume != nil {
_audioPlayer?.volume = volume!
}
//id
self.playerItemID = playerItemID
_audioPlayer?.play()
}
catch let error {
self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: error.localizedDescription)
}
}//eom
func playAudioFromUrl(_ url:URL)
{
do {
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
try sharedSession.setActive(true)
if FileManager.default.fileExists(atPath: url.path) {
_audioPlayer = try AVAudioPlayer(contentsOf: url)
_audioPlayer?.numberOfLoops = 0
_audioPlayer?.isMeteringEnabled = true
_audioPlayer?.delegate = self
//volume
if volume != nil {
_audioPlayer?.volume = volume!
}
//id
self.playerItemID = url.absoluteString
_audioPlayer?.play()
}
else {
self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: "audio file does not exist")
}
}
catch let error {
self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: error.localizedDescription)
}
}//eom
//MARK: - Player Options
func pausePlay()
{
_audioPlayer?.pause()
}//eom
func stopPlay()
{
_audioPlayer?.stop()
do {
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setActive(false)
}
catch let error {
if verbose { print("un-able to set session to inactive, error: \(error)") }
}
}//eom
//MARK: - Delegates
func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
//inactive session
do {
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setActive(false)
}
catch let error {
if verbose { print("un-able to set session to inactive, error: \(error)") }
}
//report status
if error != nil {
self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: error!.localizedDescription)
}
else {
self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: "decode error did occurred")
}
//reset
self._audioPlayer?.delegate = nil
self._audioPlayer = nil
self.playerItemID = ""
}//eom
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
//inactive session
do {
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setActive(false)
}
catch let error {
if verbose { print("un-able to set session to inactive, error: \(error)") }
}
//report status
if flag {
delegate?.audioPlayer_playbackSuccess(playerItemID: self.playerItemID)
}
else {
delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: "player finish playing with error")
}
//reset
self._audioPlayer?.delegate = nil
self._audioPlayer = nil
self.playerItemID = ""
}//eom
}//eoc

If you are going to be working with both AudioToolBox and AVFoundation, you may want to be careful with the AudioSession. AVFoundation does a lot of updates to the AudioSession on the backend.
A quick fix for your Player would be to removed any audio session calls like the below:
let sharedSession = AVAudioSession.sharedInstance()
try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
try sharedSession.setActive(true)
_audioPlayer?.numberOfLoops = 0
_audioPlayer?.isMeteringEnabled = true
For more advance audio manipulation, Check out the book Learning Core Audio by Chris Adamson, Kevin Avila

Related

AVFragmentedAssetMinder not working on iPhone device but works on simulator

We have fragmented movie data that comes through WebSecureSocket (wss://). We are writing that into a temp.mp4 and simultaneously playing the fragments that got written into the file. So, we used AVFragmentedAsset and AVFragmentedAssetMinder for this. It works as expected in the simulator. But in the device, doesn't update the duration of the asset and doesn't post the .AVAssetDurationDidChange notification. Not sure what could be the issue. Already spent 2 days figuring out this but no luck. Can someone help me with this please,
Implementation..
public class WssPlayerSource: WebSocketDelegate {
static let ASSET_MINDER = AVFragmentedAssetMinder()
private let socketClient: WebSocketStreamingClient
private var tempMovieFile:FileHandle? = nil
private var startedPlay = false
private var fragmentedAsset: AVFragmentedAsset? = nil
let player = AVPlayer()
init(withUrl url: URL) {
socketClient = WebSocketStreamingClient(wssUrl: url)
socketClient.delegate = self
do {
self.tempMovieFile = try getTemporaryMovieFileWriter()
}catch {
print("Error opening file")
}
NotificationCenter.default.addObserver(self, selector: #selector(onVideoUpdate), name: .AVAssetDurationDidChange, object: nil)
socketClient.connect()
}
// Socket delegate
public func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .binary(let data):
self.tempMovieFile?.write(data)
if !startedPlay {
startedPlay = true
DispatchQueue.global().async { [weak self] in
self?.didReceivedInitialData()
}
}
break;
default:
break;
}
}
func didReceivedInitialData() {
fragmentedAsset = AVFragmentedAsset(url: getTemporaryMovieFile()!)
fragmentedAsset?.loadValuesAsynchronously(forKeys: ["duration", "containsFragments", "canContainFragments"], completionHandler: {
WssPlayerSource.ASSET_MINDER.mindingInterval = 1
WssPlayerSource.ASSET_MINDER.addFragmentedAsset(self.fragmentedAsset!)
self.player.replaceCurrentItem(with: AVPlayerItem(asset: self.fragmentedAsset!))
self.player.play()
})
}
#objc
func onVideoUpdate() {
print("Video duration updated..")
// This is not called in device.. but in simulator it works as expected
}
func stop() {
socketClient.forceDisconnect()
NotificationCenter.default.removeObserver(self)
if let asset = self.fragmentedAsset {
WssPlayerSource.ASSET_MINDER.addFragmentedAsset(asset)
}
}
}
And
class ViewController: UIViewController {
#IBOutlet var playerView:PlayerView!
private static let SOCKET_URL = "wss://..."
private var playerSource:WssPlayerSource? = nil
private var playerLayer:AVPlayerLayer? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.playerSource = WssPlayerSource(withUrl: URL(string: ViewController.SOCKET_URL)!)
self.playerLayer = AVPlayerLayer(player: self.playerSource!.player)
self.playerLayer?.videoGravity = .resize
self.playerView.layer.addSublayer(self.playerLayer!)
}
}

How to check if device is having poor internet connection in swift

I want to check if device is having very slow internet connection.
I have used Reachability class to check for internet connection is available or not.
But here i want to check after every few sec that internet speed is not poor.
Is this possible to find and yes than how can i do it.
I have founded solution and adding it here.
Simply create class and paste it and use it where you want.
protocol NetworkSpeedProviderDelegate: class {
func callWhileSpeedChange(networkStatus: NetworkStatus)
}
public enum NetworkStatus :String
{case poor; case good; case disConnected}
class NetworkSpeedTest: UIViewController {
weak var delegate: NetworkSpeedProviderDelegate?
var startTime = CFAbsoluteTime()
var stopTime = CFAbsoluteTime()
var bytesReceived: CGFloat = 0
var testURL:String?
var speedTestCompletionHandler: ((_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void)? = nil
var timerForSpeedTest:Timer?
func networkSpeedTestStart(UrlForTestSpeed:String!){
testURL = UrlForTestSpeed
timerForSpeedTest = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(testForSpeed), userInfo: nil, repeats: true)
}
func networkSpeedTestStop(){
timerForSpeedTest?.invalidate()
}
#objc func testForSpeed()
{
testDownloadSpeed(withTimout: 2.0, completionHandler: {(_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void in
print("%0.1f; KbPerSec = \(megabytesPerSecond)")
if (error as NSError?)?.code == -1009
{
self.delegate?.callWhileSpeedChange(networkStatus: .disConnected)
}
else if megabytesPerSecond == -1.0
{
self.delegate?.callWhileSpeedChange(networkStatus: .poor)
}
else
{
self.delegate?.callWhileSpeedChange(networkStatus: .good)
}
})
}
}
extension NetworkSpeedTest: URLSessionDataDelegate, URLSessionDelegate {
func testDownloadSpeed(withTimout timeout: TimeInterval, completionHandler: #escaping (_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void) {
// you set any relevant string with any file
let urlForSpeedTest = URL(string: testURL!)
startTime = CFAbsoluteTimeGetCurrent()
stopTime = startTime
bytesReceived = 0
speedTestCompletionHandler = completionHandler
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForResource = timeout
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
guard let checkedUrl = urlForSpeedTest else { return }
session.dataTask(with: checkedUrl).resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
bytesReceived += CGFloat(data.count)
stopTime = CFAbsoluteTimeGetCurrent()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let elapsed = (stopTime - startTime) //as? CFAbsoluteTime
let speed: CGFloat = elapsed != 0 ? bytesReceived / (CGFloat(CFAbsoluteTimeGetCurrent() - startTime)) / 1024.0 : -1.0
// treat timeout as no error (as we're testing speed, not worried about whether we got entire resource or not
if error == nil || ((((error as NSError?)?.domain) == NSURLErrorDomain) && (error as NSError?)?.code == NSURLErrorTimedOut) {
speedTestCompletionHandler?(speed, nil)
}
else {
speedTestCompletionHandler?(speed, error)
}
}
}
After That how to use it.So implement delegate and use it.
class ViewController: UIViewController, NetworkSpeedProviderDelegate {
func callWhileSpeedChange(networkStatus: NetworkStatus) {
switch networkStatus {
case .poor:
break
case .good:
break
case .disConnected:
break
}
}
let test = NetworkSpeedTest()
override func viewDidLoad() {
super.viewDidLoad()
test.delegate = self
test.networkSpeedTestStop()
test.networkSpeedTestStart(UrlForTestSpeed: "Paste Your Any Working URL ")
// Do any additional setup after loading the view.
}
}
You can call the Reachability class every time after a fixed interval by using method given below:
override func viewDidLoad() {
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 'x' seconds
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}
#objc func updateCounting(){
\\Do your stuff here(Check Reachabilty Here)
}
EDIT:
This is how you can check signal strength for cellular networks.
func getSignalStrength() -> Int {
let application = UIApplication.shared
let statusBarView = application.value(forKey: "statusBar") as! UIView
let foregroundView = statusBarView.value(forKey: "foregroundView") as! UIView
let foregroundViewSubviews = foregroundView.subviews
var dataNetworkItemView:UIView? = nil
for subview in foregroundViewSubviews {
if subview.isKind(of: NSClassFromString("UIStatusBarSignalStrengthItemView")!) {
dataNetworkItemView = subview
break
}
}
if dataNetworkItemView == nil
{
return 0
}
return dataNetworkItemView?.value(forKey: "signalStrengthBars") as! Int
}
For Wifi Network this is how you can get signal strength
private func getWiFiRSSI() -> Int? {
let app = UIApplication.shared
var rssi: Int?
let exception = tryBlock {
guard let statusBar = app.value(forKey: "statusBar") as? UIView else { return }
if let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), statusBar .isKind(of: statusBarMorden) { return }
guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return }
for view in foregroundView.subviews {
if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
rssi = val
break
}
}
}
}
if let exception = exception {
print("getWiFiRSSI exception: \(exception)")
}
return rssi
}
EDIT 2: Add this extension to access your status bar view
extension UIApplication {
var statusBarUIView: UIView? {
if #available(iOS 13.0, *) {
let tag = 38482458385
if let statusBar = self.keyWindow?.viewWithTag(tag) {
return statusBar
} else {
let statusBarView = UIView(frame: UIApplication.shared.statusBarFrame)
statusBarView.tag = tag
self.keyWindow?.addSubview(statusBarView)
return statusBarView
}
} else {
if responds(to: Selector(("statusBar"))) {
return value(forKey: "statusBar") as? UIView
}
}
return nil
}
}

Swift CMMotionActivityManager not displaying correct authorization Status

I am trying to create a very simple step counter and am following Kamil Wysocki's article at https://brightinventions.pl/blog/coremotion-pedometer-swift/. However, my .isActivityAvailable() is returning False preventing any of my other functions from initiating. Also, my CMMotionActivityManager.authorizationStatus() call is returning 3 (denied). I cannot for the life of me figure out why this is happening.
I have added Motion Usage Description to my info.plist with a description and have enabled authorization on my iPhone simulator. I have also tried simulating the "City walk" mode in the debug menu, which doesn't help. My code is below.
import UIKit
import CoreMotion
import CoreLocation
class ViewController: UIViewController {
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var activityTypeLabel: UILabel!
#IBOutlet weak var stepsCountLabel: UILabel!
private let activityManager = CMMotionActivityManager()
private let pedometer = CMPedometer()
private var shouldStartUpdating: Bool = false
private var startDate: Date? = nil
override func viewDidLoad() {
super.viewDidLoad()
startButton.addTarget(self, action: #selector(didTapStartButton), for: .touchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let startDate = startDate else { return }
updateStepsCountLabelUsing(startDate: startDate)
}
#objc private func didTapStartButton() {
shouldStartUpdating = !shouldStartUpdating
shouldStartUpdating ? (onStart()) : (onStop())
}
}
extension ViewController {
private func onStart() {
startButton.setTitle("Stop", for: .normal)
startDate = Date()
checkAuthorizationStatus()
startUpdating()
}
private func onStop() {
startButton.setTitle("Start", for: .normal)
startDate = nil
stopUpdating()
}
private func startUpdating() {
if CMMotionActivityManager.isActivityAvailable() {
startTrackingActivityType()
} else {
activityTypeLabel.text = "Not available"
}
if CMPedometer.isStepCountingAvailable() {
startCountingSteps()
} else {
stepsCountLabel.text = "Not available"
}
}
private func checkAuthorizationStatus() {
switch CMMotionActivityManager.authorizationStatus() {
case CMAuthorizationStatus.denied:
onStop()
activityTypeLabel.text = "Not available"
stepsCountLabel.text = "Not available"
default:break
}
}
private func stopUpdating() {
activityManager.stopActivityUpdates()
pedometer.stopUpdates()
pedometer.stopEventUpdates()
}
private func on(error: Error) {
//handle error
}
private func updateStepsCountLabelUsing(startDate: Date) {
pedometer.queryPedometerData(from: startDate, to: Date()) {
[weak self] pedometerData, error in
if let error = error {
self?.on(error: error)
} else if let pedometerData = pedometerData {
DispatchQueue.main.async {
self?.stepsCountLabel.text = String(describing: pedometerData.numberOfSteps)
}
}
}
}
private func startTrackingActivityType() {
activityManager.startActivityUpdates(to: OperationQueue.main) {
[weak self] (activity: CMMotionActivity?) in
guard let activity = activity else { return }
DispatchQueue.main.async {
if activity.walking {
self?.activityTypeLabel.text = "Walking"
} else if activity.stationary {
self?.activityTypeLabel.text = "Stationary"
} else if activity.running {
self?.activityTypeLabel.text = "Running"
} else if activity.automotive {
self?.activityTypeLabel.text = "Automotive"
}
}
}
}
private func startCountingSteps() {
pedometer.startUpdates(from: Date()) {
[weak self] pedometerData, error in
guard let pedometerData = pedometerData, error == nil else { return }
DispatchQueue.main.async {
self?.stepsCountLabel.text = pedometerData.numberOfSteps.stringValue
}
}
}
}
Here is my viewController upon running and clicking start
Big thanks in advance for anyone who can offer any knowledge!!!
Use this code to check authorization status :
let manager = CMMotionActivityManager()
let today = Date()
manager.queryActivityStarting(from: today, to: today, to: OperationQueue.main, withHandler: { (activities: [CMMotionActivity]?, error: Error?) -> () in
if error != nil {
let errorCode = (error! as NSError).code
if errorCode == Int(CMErrorMotionActivityNotAuthorized.rawValue) {
print("NotAuthorized")
}
} else {
print("Authorized")
//Start Tracking Activity
}
manager.stopActivityUpdates()
})
Hope this will work for you.

Variable in renderer(_:didAdd:for:) not updating

I am working on a IPhone app that uses CoreML and ARKit simultaneously. The CoreML is supposed to recognize a number and the ARKit should detect a vertical plane (aka wall) and add some planes over that same wall with the content displayed on those planes depending on the recognised number.
So, the CoreML is working 100%. Everytime I "change" the number the topPrediction variable updates automatically ( so far so good ). The thing is that my variable in func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor does not update! What I mean is that the first number recognized by the CoreML is correctly sent to the renderer func and it works like a charm but if I turn the camera to another number it stills assumes that it's the first number! As you may see in the code, I even tried making a func getGabNum() -> Int and then calling it in the renderer func ( var num = getGabNum() ) but I continue getting the warning "Variable 'num' was never mutated; consider changing to 'let' constant" which means that something is not right. So guys, here is my code! Hope you can help me and thank you!
struct Room : Decodable {
let id : Int?
let num : Int?
//Adicionar Schedules
var horario = [Schedule]()
}
struct Schedule : Decodable {
let id : Int?
let hora_ini : Date?
let hora_fim : Date?
let descr : String?
private enum CodingKeys: String, CodingKey {
case id
case hora_ini
case hora_fim
case descr
}
}
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet weak var debugLabel: UILabel!
#IBOutlet weak var debugTextView: UITextView!
#IBOutlet weak var sceneView: ARSCNView!
let dispatchQueueML = DispatchQueue(label: "com.hw.dispatchqueueml") // A Serial Queue
var visionRequests = [VNRequest]()
var room: Room?
var room_array: [[Int]] = [[17, 0], [43, 0], [120,0]]
var teste = 0
var num = -1
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
configureLighting()
guard let selectedModel = try? VNCoreMLModel(for: SalasMLv6().model) else {
fatalError("Could not load model.")
}
let classificationRequest = VNCoreMLRequest(model: selectedModel, completionHandler: classificationCompleteHandler)
classificationRequest.imageCropAndScaleOption = VNImageCropAndScaleOption.centerCrop // Crop from centre of images and scale to appropriate size.
visionRequests = [classificationRequest]
loopCoreMLUpdate()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setUpSceneView()
}
func setUpSceneView()
{
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .vertical
sceneView.session.run(configuration)
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
func configureLighting()
{
sceneView.automaticallyUpdatesLighting = true
sceneView.autoenablesDefaultLighting = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - ARSCNViewDelegate
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
DispatchQueue.main.async {
// Do any desired updates to SceneKit here.
}
}
func loopCoreMLUpdate() {
dispatchQueueML.async {
self.updateCoreML()
self.loopCoreMLUpdate()
}
}
func updateCoreML() {
// Get Camera Image as RGB
let pixbuff : CVPixelBuffer? = (sceneView.session.currentFrame?.capturedImage)
if pixbuff == nil { return }
let ciImage = CIImage(cvPixelBuffer: pixbuff!)
// Prepare CoreML/Vision Request
let imageRequestHandler = VNImageRequestHandler(ciImage: ciImage, options: [:])
// Run Vision Image Request
do {
try imageRequestHandler.perform(self.visionRequests)
} catch {
print(error)
}
}
func classificationCompleteHandler(request: VNRequest, error: Error?) {
// Catch Errors
if error != nil {
print("Error: " + (error?.localizedDescription)!)
return
}
guard let observations = request.results else {
print("No results")
return
}
// Get Classifications
let classifications = observations[0...2] // top 3 results
.compactMap({ $0 as? VNClassificationObservation })
.map({ "\($0.identifier) \(String(format:" : %.2f", $0.confidence))" })
.joined(separator: "\n")
// Render Classifications
DispatchQueue.main.async {
// Display Debug Text on screen
self.debugTextView.text = "TOP 3 PROBABILITIES: \n" + classifications
// Display Top Symbol
var symbol = "❌"
var gabNum: Int?
let topPrediction = classifications.components(separatedBy: "\n")[0]
let topPredictionName = topPrediction.components(separatedBy: ":")[0].trimmingCharacters(in: .whitespaces)
// Only display a prediction if confidence is above 90%
let topPredictionScore:Float? = Float(topPrediction.components(separatedBy: ":")[1].trimmingCharacters(in: .whitespaces))
if (topPredictionScore != nil && topPredictionScore! > 0.05) {
if (topPredictionName == "120") {
symbol = "1️⃣2️⃣0️⃣"
gabNum = 120
self.teste = gabNum!
}
if (topPredictionName == "43") {
symbol = "4️⃣3️⃣"
gabNum = 43
self.teste = gabNum!
}
if (topPredictionName == "17") {
symbol = "1️⃣7️⃣"
gabNum = 17
self.teste = gabNum!
}
}
if let gn = gabNum {
// get room from REST
let jsonURL = "someURL\(gn)"
guard let url = URL(string: jsonURL) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil{
print("error)")
return
}
do {
self.room = try JSONDecoder().decode(Room.self, from: data!)
}catch{
print(“Decoder Error”)
}
}.resume()
}
self.debugLabel.text = symbol
}
}
// MARK: - HIDE STATUS BAR
override var prefersStatusBarHidden : Bool { return true }
func getGabNum() -> Int {
return self.teste
}
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)
{
guard room != nil else {
print("room == nil")
return
}
guard let planeAnchor = anchor as? ARPlaneAnchor else {
return
}
num = getGabNum()
if( num == room_array[0][0] && room_array[0][1] == 1 ){
return
}else{
if( num == room_array[1][0] && room_array[1][1] == 1 ){
return
}else{
if( num == room_array[2][0] && room_array[2][1] == 1 ){
return
}else{
var i = 0
for horario in (self.room?.horario)!{
// Planes and Nodes Stuff Right Here
}
switch self.room?.num{
case 17: room_array[0][1] = 1
case 43: room_array[1][1] = 1
case 120:room_array[2][1] = 1
}
}
}
}
}
}

How do I know that the contacts have been modified, if the application was not active on iOS?

I need to run a function when the contacts have changed. If the application is active, you can do this with NotificationCenter as narrated in this post (sometimes It works when I add a new number to an existing contact). How do I know that the contact (or contacts) have been changed after the launch of the application?
I made the following functions for my task
#objc private func matchingContacts() {
if isSuccessContactUploading {
contactManager.matchingContacts(notMatch: { [weak self] in
guard let _self = self else { return }
debugPrint("matchingContacts != equals")
_self.isSuccessContactUploading = false
_self.syncContacts()
})
}
}
These functions are in ContactManager
func matchingContacts(notMatch: (() -> Void)?) {
getContacts { (contacts, error) in
if error == nil {
debugPrint("contacts count", contacts.count)
self.getContactsDictionaryFromCache(contacts, notMatch: {
notMatch?()
})
}
}
}
private func getContactsDictionaryFromCache(_ contacts: [CNContact], notMatch: (() -> Void)?) {
var isMatching = true
for contact in contacts {
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
if let contactDictionary = cache[key] {
if !contactDictionary.isEqual(to: contact.dictionary) {
debugPrint("contactDictionary not matching")
isMatching = false
}
} else {
debugPrint("contactDictionary isn't here")
isMatching = false
}
} catch {
debugPrint(error.localizedDescription)
isMatching = false
}
}
if !isMatching {
notMatch?()
}
cacheContacts(contacts)
}
private func cacheContacts(_ contacts: [CNContact]) {
for contact in contacts {
let contactDictionary = contact.dictionary as NSDictionary
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
cache[key] = contactDictionary
} catch {
debugPrint(error.localizedDescription)
}
}
}

Resources