How do I keep my application running in the background?
Would I have to jailbreak my iPhone to do this? I just need this app to check something from the internet every set interval and notify when needed, for my own use.
Yes, no need to jailbreak. Check out the "Implementing long-running background tasks" section of this doc from Apple.
From Apple's doc:
Declaring Your App’s Supported Background Tasks
Support for some types of background execution must be declared in advance by the app that uses them. An app declares support for a service using its Info.plist file. Add the UIBackgroundModes key to your Info.plist file and set its value to an array containing one or more of the following strings: (see Apple's doc from link mentioned above.)
I guess this is what you required
When an iOS application goes to the background, are lengthy tasks paused?
iOS Application Background Downloading
This might help you ...
Enjoy Coding :)
Use local notifications to do that. But this will not check every time. You will have to set a time where you will check your specific event, you may shorten this by decreasing your time slot. Read more about local notification to know how to achieve this at:
http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction/Introduction.html
I found a way, to keep app running in background by playing silence
Make sure, that you selected audio playback in background modes
Also, don't use this method for long time, since it consumes CPU resources and battery juice, but I think it's a suitable way to keep app alive for a few minutes.
Just create an instance of SilencePlayer, call play() and then stop(), when you done
import CoreAudio
public class SilencePlayer {
private var audioQueue: AudioQueueRef? = nil
public private(set) var isStarted = false
public func play() {
if isStarted { return }
print("Playing silence")
let avs = AVAudioSession.sharedInstance()
try! avs.setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
try! avs.setActive(true)
isStarted = true
var streamFormat = AudioStreamBasicDescription(
mSampleRate: 16000,
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
mBytesPerPacket: 2,
mFramesPerPacket: 1,
mBytesPerFrame: 2,
mChannelsPerFrame: 1,
mBitsPerChannel: 16,
mReserved: 0
)
let status = AudioQueueNewOutput(
&streamFormat,
SilenceQueueOutputCallback,
nil, nil, nil, 0,
&audioQueue
)
print("OSStatus for silence \(status)")
var buffers = Array<AudioQueueBufferRef?>.init(repeating: nil, count: 3)
for i in 0..<3 {
buffers[i]?.pointee.mAudioDataByteSize = 320
AudioQueueAllocateBuffer(audioQueue!, 320, &(buffers[i]))
SilenceQueueOutputCallback(nil, audioQueue!, buffers[i]!)
}
let startStatus = AudioQueueStart(audioQueue!, nil)
print("Start status for silence \(startStatus)")
}
public func stop() {
guard isStarted else { return }
print("Called stop silence")
if let aq = audioQueue {
AudioQueueStop(aq, true)
audioQueue = nil
}
try! AVAudioSession.sharedInstance().setActive(false)
isStarted = false
}
}
fileprivate func SilenceQueueOutputCallback(_ userData: UnsafeMutableRawPointer?, _ audioQueueRef: AudioQueueRef, _ bufferRef: AudioQueueBufferRef) -> Void {
let pointer = bufferRef.pointee.mAudioData
let length = bufferRef.pointee.mAudioDataByteSize
memset(pointer, 0, Int(length))
if AudioQueueEnqueueBuffer(audioQueueRef, bufferRef, 0, nil) != 0 {
AudioQueueFreeBuffer(audioQueueRef, bufferRef)
}
}
Tested on iOS 10 and Swift 4
I know this is not the answer to your question, but I think it is a solution.
This assumes that your trying to check something or get data from the internet on a regular basis?
Create a service that checks the internet every set interval for whatever it is you want to know, and create a push notification to alert you of it, if the server is down, or whatever it is your trying to monitor has changed state. Just an idea.
Yes you can do something like this. For that you need to set entry in info.plist to tell os that my app will run in background. I have done this while I wanted to pass user's location after particular time stamp to server. For that I have set "Required background modes" set to "App registers for location updates".
You can write a handler of type UIBackgroundTaskIdentifier.
You can already do this in the applicationDidEnterBackground Method
Related
I'm using AudioKit to run an AKSequencer() that plays both mp3 and wav files using AKMIDISampler(). Everything works great, except in cases when the app has entered background state for more than 30+ min, and then brought back up again for use. It seems to then lose all of it's audio connections and plays the "missing file" sinewave tone mentioned in other threads. The app can happily can enter background momentarily, user can quit, etc without the tone. It seems to only happen when left in background for long periods of time and then brought up again.
I've tried changing the order of AudioKit.start() and file loading, but nothing seems to completely eliminate the issue.
My current workaround is simply to prevent the user's display from timing out, however that does not address many use-cases of the issue occurring.
Is there a way to handle whatever error I'm setting up that creates this tone? Here is a representative example of what I'm doing with ~40 audio files.
//viewController
override func viewDidLoad() {
sequencer.setupSequencer()
}
class SamplerWav {
let audioWav = AKMIDISampler()
func loadWavFile() {
try? audioWav.loadWav("some_wav_audio_file")
}
class SamplerMp3 {
let audioMp3 = AKMIDISampler()
let audioMp3_akAudioFile = try! AKAudioFile(readFileName: "some_other_audio_file.mp3")
func loadMp3File() {
try? audioMp3.loadAudioFile(audioMp3_akAudioFile)
}
class Sequencer {
let mixer = AKMixer()
let subMix = AKMixer()
let samplerWav = SamplerWav()
let samplerMp3 = SamplerMp3()
var callbackTrack: AKMusicTrack!
let callbackInstr = AKMIDICallbackInstrument()
func setupSequencer{
AudioKit.output = mixer.mixer
try! AudioKit.start()
callbackTrack = sequencer.newTrack()
callbackTrack?.setMIDIOutput(callbackInstr.midiIn)
samplerWav.loadWavFile()
samplerMp3.loadMp3File()
samplerWav.audioWav >>> subMix
samplerMp3.audioMp3 >>> submix
submix >>> mixer
}
//Typically run from a callback track
func playbackSomeSound(){
try? samplerWav.audioWav.play(noteNumber: 60, velocity: 100, channel: 1)
}
}
Thanks! I'm a big fan of AudioKit.
After some trial and error, here's a workflow that seems to address the issue for my circumstance:
-create my callback track(s) -once- from viewDidLoad
-stop AudioKit, and call .detach() on all my AKMIDISampler tracks and any routing in willResignActive
-start AudioKit (again), and reload and reroute all of the audio files/tracks from didBecomeActive
I implemented iBeacon recognition in my project, everything works fine. When the application connects to the BLE device that has IBeacon, then I launch a whole process that sends data to a server.
I'm looking for a way to know when iOS kill the application after 10 seconds to perform one last action.
I'm looking at the functions in the CLLocationManagerDelegate protocol, but I can not find a function that might look like willDisconnectApplication
How can I know a second before iOS kill my application to run a code?
You can use a code snippet like below to track how much background time you have available.
Just by starting the background task shown, iOS will also give you extra background running time, so you get 180 seconds instead of 10 seconds. After doing this, you also get to track when that time is about to expire by looking at UIApplication.shared.backgroundTimeRemaining.
private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
func extendBackgroundRunningTime() {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return
}
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
NSLog("Background running expired by iOS. Cannot detect beacons again until a new region event")
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
})
if threadStarted {
NSLog("Background task thread already started.")
}
else {
threadStarted = true
DispatchQueue.global().async {
let startedTime = Int(Date().timeIntervalSince1970) % 10000000
NSLog("Background task thread started")
while (true) {
let backgroundTimeRemaining = UIApplication.shared.backgroundTimeRemaining;
if (backgroundTimeRemaining < 200.0) {
if (backgroundTimeRemaining.truncatingRemainder(dividingBy: 30) < 1) {
NSLog("Thread \(startedTime) background time remaining: \(backgroundTimeRemaining)")
}
else {
NSLog("Thread \(startedTime) background time remaining: \(backgroundTimeRemaining)")
}
}
Thread.sleep(forTimeInterval: 1);
}
}
}
}
I'm finally starting to play with Swift (and macOS development) for the first time. I'm trying to write a simple screen capture app to get started. I've already taken a look at and succeeded in using the AVFoundation APIs for doing this (AVCaptureSession, AVCaptureScreenInput, etc). But now I'd like to attempt to go a little lower-level and play with the closer-to-the-metal CGDisplayStream API.
Unfortunately I've only been able to get it to capture a single frame. I suspect I may be missing something regarding potential interaction between the main Runloop and the DispatchQueue I'm passing in? Not really clear on if those things interact in the first place.
Here's a small reproduction of my issue:
import Foundation
import AVFoundation
import CoreGraphics
let mainDisplay = CGMainDisplayID()
let displayBounds = CGDisplayBounds(mainDisplay)
let recordingQueue = DispatchQueue.global(qos: .background)
let displayStreamProps : [CFString : Any] = [
CGDisplayStream.preserveAspectRatio: kCFBooleanTrue,
CGDisplayStream.showCursor: kCFBooleanTrue,
CGDisplayStream.minimumFrameTime: 60,
]
let displayStream = CGDisplayStream(
dispatchQueueDisplay: mainDisplay,
outputWidth: Int(displayBounds.width),
outputHeight: Int(displayBounds.height),
pixelFormat: Int32(kCVPixelFormatType_32BGRA),
properties: displayStreamProps as CFDictionary,
queue: recordingQueue,
handler: { status, displayTime, frameSurface, updateRef in
print("is only called once")
}
)
func quit(_ : Int32) {
displayStream?.stop()
}
signal(SIGINT, quit)
displayStream?.start()
RunLoop.current.run()
Any help would be massively appreciated!!
Removing this line seems to fix the issue:
CGDisplayStream.minimumFrameTime: 60,
The docs don't mention what the unit for this "time" field is, but it appears to be in seconds. So you could change it to 1.0/60.0 for 60fps capture.
I want to stop a the user's Macbook from automatically sleeping while one the app is running. How would I do that? I have seen this
https://developer.apple.com/library/content/qa/qa1340/_index.html
and this:
Disable sleep mode in OS X with swift
and this:
Using Swift to disable sleep/screen saver for OSX
But there is no simple line of code that I can put or a delegate method that makes this simple like there is in iOS? I just want to have the entire app work properly and stop the computer from automatically going to sleep. If someone puts in it to sleep manually, then obviously that is fine. I don't want to prevent the user from putting the computer to sleep. But as long as my app is running I don't want the computer to sleep. Like when you are watching a movie, the app does not go to sleep. How do I do that?
I feel like this is the code that I need but do I just run this function in the viewdidload and it will work? Is it that simple?
var assertionID: IOPMAssertionID = 0
var success: IOReturn?
func disableScreenSleep(reason: String = "Unknown reason") -> Bool? {
guard success != nil else { return nil }
success = IOPMAssertionCreateWithName( kIOPMAssertionTypeNoDisplaySleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
reason as CFString,
&assertionID )
return success == kIOReturnSuccess
}
As I already mentioned in comments you need to remove the guard otherwise it will simply return nil and will never disable the screen sleep. You can also simplify your method, return void and change your success property to a Bool property like disabled to monitor the screen sleep state:
var assertionID: IOPMAssertionID = 0
var sleepDisabled = false
func disableScreenSleep(reason: String = "Disabling Screen Sleep") {
if !sleepDisabled {
sleepDisabled = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), reason as CFString, &assertionID) == kIOReturnSuccess
}
}
func enableScreenSleep() {
if sleepDisabled {
IOPMAssertionRelease(assertionID)
sleepDisabled = false
}
}
To disable the screen simply just call the method inside viewDidLoad of your first view controller:
override func viewDidLoad() {
super.viewDidLoad()
disableScreenSleep()
print("sleep Disabled:", sleepDisabled)
}
I am working on NEHotspotHelper and trying to register but not receiving call back. Firstly,
I enabled Capability : Network Extensions
Then added this following code,
let options: [String: NSObject] = [kNEHotspotHelperOptionDisplayName : "ABC" as NSObject]
let queue: DispatchQueue = DispatchQueue(label: "com.ABC", attributes: DispatchQueue.Attributes.concurrent)
NSLog("Started wifi scanning.")
NEHotspotHelper.register(options: options, queue: queue) { (cmd: NEHotspotHelperCommand) in
NSLog("Received command: \(cmd.commandType.rawValue)")
if cmd.commandType == NEHotspotHelperCommandType.filterScanList {
//Get all available hotspots
let list: [NEHotspotNetwork] = cmd.networkList!
//Figure out the hotspot you wish to connect to
print(list)
} else if cmd.commandType == NEHotspotHelperCommandType.evaluate {
if let network = cmd.network {
//Set high confidence for the network
network.setConfidence(NEHotspotHelperConfidence.high)
let response = cmd.createResponse(NEHotspotHelperResult.success)
response.setNetwork(network)
response.deliver() //Respond back
}
} else if cmd.commandType == NEHotspotHelperCommandType.authenticate {
//Perform custom authentication and respond back with success
// if all is OK
let response = cmd.createResponse(NEHotspotHelperResult.success)
response.deliver() //Respond back
}
}
Kindly let me know if I am missing any step.
You should check the result of the register() function. If it's returning false, something is probably not configured correctly. See the full list of configuration instructions below.
Also in the screenshot you provided, you have the entitlements enabled for Hotspot Configuration, but the API you're calling is for Hotspot Helper. The two features require very different entitlements. You'll need to make sure everything is configured for Hotspot Helper to call that API. Again, see below for full details. See Hotspot Helper vs. Hotspot Configuration for more details about the differences of these similarly named APIs.
To use NEHotspotHelper:
Apply for the Network Extension entitlement.
This needs to be done at Apple's website here.
Modify your Provisioning Profile.
Go to http://developer.apple.com. Hit Edit near your profile. On the bottom where it says Entitlements, choose the one that contains the Network Extension entitlement.
Update your app's entitlements file.
The application must set com.apple.developer.networking.HotspotHelper as one of its entitlements. The value of the entitlement is a boolean set to true.
Add Background Mode
The application's Info.plist must include a UIBackgroundModes array containing network-authentication.
Note that unlike all the other background modes that are converted to human readable strings, this one will stay as network-authentication.
Call the NEHotspotHelper.register() function.
This method should be called once when the application starts up. Invoking it again will have no effect and result in false being returned.
You should make sure the function returns true. Otherwise something one of the above steps is probably not configured properly.
Understand when this callback will be called.
From the documentation, it's not entirely clear when exactly this callback will be called. For example, one might assume that NEHotspotHelper could be used to monitor for network connections. However, the callback will (only?) be called when the user navigates to the Settings app and goes to the Wi-Fi page.
Since your callback will be called only while the user in the Settings app, you should attach to the debugger and use print().
Swift Example
let targetSsid = "SFO WiFi"
let targetPassword = "12345678"
let targetAnnotation: String = "Acme Wireless"
let options: [String: NSObject] = [
kNEHotspotHelperOptionDisplayName: targetAnnotation as NSString
]
let queue = DispatchQueue(label: "com.example.test")
let isAvailable = NEHotspotHelper.register(options: options, queue: queue) { (command) in
switch command.commandType {
case .evaluate,
.filterScanList:
let originalNetworklist = command.networkList ?? []
let networkList = originalNetworklist.compactMap { network -> NEHotspotNetwork? in
print("networkName: \(network.ssid); strength: \(network.signalStrength)")
if network.ssid == targetSsid {
network.setConfidence(.high)
network.setPassword(targetPassword)
return network
}
return nil
}
let response = command.createResponse(.success)
response.setNetworkList(networkList)
response.deliver()
default:
break
}
}
assert(isAvailable)
Sources:
https://developer.apple.com/documentation/networkextension/nehotspothelper/1618965-register
https://medium.com/#prvaghela/nehotspothelper-register-an-app-as-a-hotspot-helper-cf92a6ed7b72
https://stackoverflow.com/a/39189063/35690