I have been working on an enterprise iOS/Swift (iOS 11.3, Xcode 9.3.1) app in which I want to be notified if the screen changes (goes blank or becomes active) and capture the events in a Realm databse. I am using the answer from tbaranes in detect screen unlock events in IOS Swift and it works, but I find added repeats as the screen goes blank and becomes active:
Initial Blank: a single event recorded
Initial Re-activiation: two events are recorded
Second Blank: two events are recorded
Second Re-act: three events are recorded
and this cycle of adding an additional event recording each cycle.
This must be something in the code (or missing from the code) that is causing an additive effect but I can’t find it. And, yes, the print statements show the issue is not within the Realm database, but are actual repeated statements.
My code is below. Any suggestions are appreciated.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
Unmanaged.passUnretained(self).toOpaque(), // observer
displayStatusChangedCallback, // callback
"com.apple.springboard.hasBlankedScreen" as CFString, // event name
nil, // object
.deliverImmediately)
}
private let displayStatusChangedCallback: CFNotificationCallback = { _, cfObserver, cfName, _, _ in
guard let lockState = cfName?.rawValue as String? else {
return
}
let catcher = Unmanaged<AppDelegate>.fromOpaque(UnsafeRawPointer(OpaquePointer(cfObserver)!)).takeUnretainedValue()
catcher.displayStatusChanged(lockState)
print("how many times?")
}
private func displayStatusChanged(_ lockState: String) {
// the "com.apple.springboard.lockcomplete" notification will always come after the "com.apple.springboard.lockstate" notification
print("Darwin notification NAME = \(lockState)")
if lockState == "com.apple.springboard.hasBlankedScreen" {
print("A single Blank Screen")
let statusString = dbSource() // Realm database
statusString.infoString = "blanked screen"
print("statusString: \(statusString)")
statusString.save()
return
}
Related
Is there any way I can temporarily disable the drawing of the UI (and the progression of animations and other related things)?
I have a notification system which coalesces notifications, so that multiple notifications which happen in a single run loop cycle are coalesced into one for the interested party, kind of like how setNeedsDisplay can be called any number of times in a single cycle, but the drawing only happens once.
The problem is, the way I'm coalescing notifications is to schedule the forward notification on the next run loop, and skip new updates in the meantime, kind of like this:
private var startedCoalescingUpdates = false
func receiveNotification() {
guard startedCoalescingUpdates == false else {
return
}
startedCoalescingUpdates = true
OperationQueue.main.addOperation {
self.setForwardNotification = false
self.delegate.updateOccurred()
}
}
Notifications can be chained together, so that one notification triggers another notification, which can trigger another, etc.
Because OperationQueue.main.addOperation happens on the next cycle (or at least it does if I call it from another block which was scheduled in addOperation), some UI drawing occurs whilst the notifications propagate. The UI can show several frames which look incorrect; I'd rather not draw the UI or respond to events, etc, until the notifications are all done, in order for all the data to be fully ready before its drawn for the first time. I'd probably do something like:
private var startedCoalescingUpdates = false
func receiveNotification() {
guard startedCoalescingUpdates == false else {
return
}
startedCoalescingUpdates = true
//////
stopDrawingUI()
//////
OperationQueue.main.addOperation {
self.setForwardNotification = false
//////
resumeDrawingUI()
//////
self.delegate.updateOccurred()
}
}
Is this possible?
I use the following codes for printing in the app:
init() {
self.printInfo.outputType = UIPrintInfoOutputType.photo
self.printInfo.orientation = UIPrintInfoOrientation.landscape
self.printController.printInfo = self.printInfo
self.printer = UIPrinter(url: URL(string: printIP)!)
// where printIP is a string that give the internal IP of the printer
debugPrint(printIP)
}
func print(image: UIImage) -> Bool {
self.printController.printingItem = image
printController.print(to: printer, completionHandler: {(controller, success, error) -> Void in
if success {
debugPrint("Printing Completed.")
} else {
debugPrint("Printing Failed.")
}
})
return true
}
It can print successfully. However, when the function is triggered, there is an alert box indicating that it is contacting to the Printer, and printing. Is there any method to avoid the pop up of this alert box? I want the printing done at the back without showing anything on the screen that interfere the user experience (I want to play a movie when the printer is working at the back).
Thanks.
From iOS 8 there is a way to print without any presentation of the
printing UI. Instead of presenting the UI each time the user presses a
print button, you can provide a way for your users to select a printer
somewhere in your app with the easy-to-use UIPrinterPickerController.
It accepts an optional UIPrinter instance in its constructor for a
pre-selection, uses the same presentation options as explained above,
and has a completion handler for when the user has selected her
printer:
Swift 3
let printerPicker = UIPrinterPickerController(initiallySelectedPrinter: savedPrinter)
printerPicker.present(animated: true) {
(printerPicker, userDidSelect, error) in
if userDidSelect {
self.savedPrinter = printerPicker.selectedPrinter
}
}
Now you can tell your UIPrintInteractionController to print directly by calling printToPrinter(:completionHandler:) with the saved printer instead of using one of the present... methods.
Source:- http://nshipster.com/uiprintinteractioncontroller/
I don't really understand when messageLostHandler is triggered on the subscriptionWithMessageFoundHandler method.
This is my code:
func suscribeToNearbyDevices(myUserId: String){
subscription = messageMgr?.subscription(messageFoundHandler: { (message: GNSMessage?) in
if let incomingMessage = message, let content = incomingMessage.content {
if let userIdEncoded = String(data: content, encoding: String.Encoding.utf8) {
NotificationCenter.default.post(name: Notification.Name(rawValue: CommunicationVariables.newUserNotificationKey), object: nil,
userInfo:userIdEncoded)
}}, messageLostHandler: { (message: GNSMessage?) in
if let incomingMessage = message, let content = incomingMessage.content {
if let userIdEncoded = String(data: content, encoding: String.Encoding.utf8) {
NotificationCenter.default.post(name: Notification.Name(rawValue: CommunicationVariables.exitUserNotificationKey), object: nil,
userInfo: [CommunicationVariables.userIdNotificationField:userIdEncoded])
}
}
}, paramsBlock:{(params:GNSSubscriptionParams?) in
guard let params = params else { return }
params.strategy = GNSStrategy(paramsBlock: { (params: GNSStrategyParams?) in
guard let params = params else { return }
params.allowInBackground = true
})
})
}
I have two iphones, If I have the two apps on the foreground, they can see each other. When I press home in one, the messageLostHandler is triggered, but if I walk out of range (like outside-of-the-house-out-of-range) the messageLostHandler is never triggered.
Why? What cause the messageLostHandler to be triggered?
Thanks!
I see that your subscription is configured to work in the background, but is the publication also configured for background? If not, then when you press the Home button on the publishing device, the app is backgrounded, causing the publication to be disabled, and the subscriber receives a call to the messageLostHandler block.
Regarding the out-of-range problem: BLE (Bluetooth Low Energy) works at a very high range (up to 100m outdoors, and less when there are obstacles). So you have to walk very far for the devices to be completely out of range. An alternative is to place one of the devices inside a fully closed metal box (a faraday cage), which blocks all radio transmissions. Within 2-3 minutes, the subscriber should receive a call to the messageLostHandler block. (Incidentally, the 2-3 minute timeout is rather long, and we're have a discussion of whether we should shorten it.)
Let me know if you need more help figuring out what's going on.
Dan
I have successfully connected a steel series Nimbus dual analog controller to use for testing in both my iOS and tvOS apps. But I am unsure about how to properly set up the valueChangeHandler portion of my GCController property.
I understand so far that there are microGamepad, gamepad and extendedGamepad classes of controllers and the differences between them. I also understand that you can check to see if the respective controller class is available on the controller connected to your device.
But now I am having trouble setting up valueChangeHandler because if I set the three valueChangeHandler portions like so, then only the valueChangeHandler that works is the last one that was loaded in this sequence:
self.gameController = GCController.controllers()[0]
self.gameController.extendedGamepad?.valueChangedHandler = { (gamepad, element) -> Void in
if element == self.gameController.extendedGamepad?.leftThumbstick {
//Never gets called
}
}
self.gameController.gamepad?.valueChangedHandler = { (gamepad, element) -> Void in
if element == self.gameController.gamepad?.dpad {
//Never gets called
}
}
self.gameController.microGamepad?.valueChangedHandler = { (gamepad, element) -> Void in
if element == self.gameController.microGamepad?.dpad {
//Gets called
}
}
If I switch them around and call self.gameController.extendedGamepad.valueChangeHandler... last, then those methods will work and the gamepad and microGamepad methods will not.
Anyone know how to fix this?
You test which profile is available and depending on the profile, you set the valueChangedHandler.
It's important to realise that the extendedGamepad contains most functionality and the microGamepad least (I think the microGamepad is only used for the AppleTV remote). Therefore the checks should be ordered differently. An extendedGamepad has all functionality of the microGamepad + additional controls so in your code the method would always enter the microGamepad profile.
Apple uses the following code in the DemoBots example project:
private func registerMovementEvents() {
/// An analog movement handler for D-pads and movement thumbsticks.
let movementHandler: GCControllerDirectionPadValueChangedHandler = { [unowned self] _, xValue, yValue in
// Code to handle movement here ...
}
#if os(tvOS)
// `GCMicroGamepad` D-pad handler.
if let microGamepad = gameController.microGamepad {
// Allow the gamepad to handle transposing D-pad values when rotating the controller.
microGamepad.allowsRotation = true
microGamepad.dpad.valueChangedHandler = movementHandler
}
#endif
// `GCGamepad` D-pad handler.
// Will never enter here in case of AppleTV remote as the AppleTV remote is a microGamepad
if let gamepad = gameController.gamepad {
gamepad.dpad.valueChangedHandler = movementHandler
}
// `GCExtendedGamepad` left thumbstick.
if let extendedGamepad = gameController.extendedGamepad {
extendedGamepad.leftThumbstick.valueChangedHandler = movementHandler
}
}
This is for a tweak, so the target is jailbroken devices, and not the app store.
I have tried hooking different methods in the SBWiFiManager but they either are called when the wifi strength changes (so continuously) or after quite delay after the network has changed.
Is there any other way to get a notification (or another method to hook) went the wifi network changes?
I know you can get the current SSID with public APIs now, but I need to be told when it changes.
One way to do this is to listen for the com.apple.system.config.network_change event from the Core Foundation Darwin notification center.
Register for the event:
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
onNotifyCallback, // callback
CFSTR("com.apple.system.config.network_change"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
Here's a sample callback:
static void onNotifyCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
NSString* notifyName = (NSString*)name;
// this check should really only be necessary if you reuse this one callback method
// for multiple Darwin notification events
if ([notifyName isEqualToString:#"com.apple.system.config.network_change"]) {
// use the Captive Network API to get more information at this point
// https://stackoverflow.com/a/4714842/119114
} else {
NSLog(#"intercepted %#", notifyName);
}
}
See my link to another answer on how to use the Captive Network API to get the current SSID, for example.
Note that although the phone I tested this on is jailbroken (iOS 6.1), I don't think this requires jailbreaking to work correctly. It certainly doesn't require the app being installed outside the normal sandbox area (/var/mobile/Applications/*).
P.S. I haven't tested this exhaustively enough to know whether this event gives any false positives (based on your definition of a network change). However, it's simple enough just to store some state variable, equal to the last network's SSID, and compare that to the current one, whenever this event comes in.
SWIFT 4.1 version
I've extend my Reachability class with this functions:
let notificationName = "com.apple.system.config.network_change"
func onNetworkChange(_ name : String) {
if (name == notificationName) {
// Do your stuff
print("Network was changed")
}
}
func registerObserver() {
let observer = UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer,
{ (nc, observer, name, _, _) -> Swift.Void in
if let observer = observer, let name = name {
let instance = Unmanaged<Reachability>.fromOpaque(observer).takeUnretainedValue()
instance.onNetworkChange(name.rawValue as String)
} },
notificationName as CFString, nil, .deliverImmediately)
}
func removeObserver() {
let observer = UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())
CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer, nil, nil)
}
Register observer on init and remove on deinit.
Unfortunately there is no additional info about what exactly was changed but we take a chance to test current SSID for example.
Hope this will helpful for somebody)