I have a native iOS module that needs to fire events periodically. These events work perfectly... sometimes. Randomly, they just dont fire.
In Obj-C, The button fires an event on the NSNotificationCenter defaultCenter, with a TiBlob image. My module class listens for this notification, and calls the following function:
- (void) imageCaptured:(NSNotification *) notification
{
NSLog(#"[INFO] module notified of image capture event");
if ([self _hasListeners:IMAGECAPTUREDEVENT])
[self fireEvent:IMAGECAPTUREDEVENT withObject:notification.userInfo];
else
NSLog(#"[INFO] if an image is captured, and there is no one there to listen for it, does an event get fired?");
}
And then in JS I have an eventlistener like so:
mymodule.addEventListener('imageCaptured', function(e){
Ti.API.info('image capture event caught in JS.);
});
This does work some of the time. I can even see the Ti.blob object returned if I add that into the listener, but other times... I see the "[INFO] module notified of image capture event" log, and then nothing. No errors. No clever line about there being no listeners. And no "image capture event caught in JS."
Related
I have a WKWebView with a custom implementation of undo and redo. I would like to be able to know when the system undo / redo are triggered (via gestures or via tapping on the keyboard assistant button in iPadOS) so that I can use my custom implementation.
Is there a public API to do that?
There are multiple ways. On the iOS native side there is no public API available to take over full control AFAIK, but you can listen to UNDO notifications like this to get to know about them:
[NSNotificationCenter.defaultCenter addObserverForName:NSUndoManagerWillUndoChangeNotification object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
NSLog(#"Undo Notification: %#", note);
}];
You will then see that the NSUndoManager responsible for that lives in WKContentView. To get there only swizzling helps with the known risks...
But there is another way that works in WebKit based browser views (as WKWebView is) and this is to listen to beforeinput events. For example for a contenteditable element you can add the following listener:
editor.addEventListener('beforeinput', event => {
if (event.inputType === 'historyUndo') {
console.log('undo')
event.preventDefault()
}
if (event.inputType === 'historyRedo') {
console.log('redo')
event.preventDefault()
}
console.log('some other inputType', event.inputType)
})
This idea was was taken from this discussion: https://discuss.prosemirror.net/t/native-undo-history/1823/3?u=holtwick
Here is a JSFiddle to test: https://jsfiddle.net/perenzo/bhztrgw3/4/
See also the corresponding TipTap plugin: https://github.com/scrumpy/tiptap/issues/468
If we instantiate session (not event, action or screen) tracking with Google Analytics for an iOS or Watch app, is there any way to specify what should and shouldn't be tracked as a session for example not to call the start lifecycle tracking on each launch -- e.g. I don't want background tasks or resuming the watch app or activating the glance to count as sessions. As a developer, do I have control over it to filer the session tracking data before sending to Google or all session tracking data are automatically dispatched to Google? currently I call this function in AppDelegate didFinishLaunchingWithOptions
(void) initializeGoogleAnalytics: (NSString * ) containerFilename {
if ([self googleTagManager] == nil) {
TAGManager * tagManager = [TAGManager instance];
[tagManager setLogger: [TWNGTMLogger logger]];
[tagManager setDispatchInterval: 20.0];
[tagManager setDispatchInterval: 1.0];
[self setGoogleTagManager: tagManager];
//Open GTM tag container (async)
[TAGContainerOpener openContainerWithId: containerFilename tagManager: [self googleTagManager] openType: kTAGOpenTypePreferFresh timeout: nil notifier: self];
DMLogInfo(DebugLogTypeTracking, # "Google Analytics initialized, container: %#, version: %#", containerFilename, kGAIVersion);
}
}
1) Is there a way to turn off or turn on Session Tracking in GTM code on iOS?
There's not a way to adjust session tracking itself using the GTM UI or the datalayer, other than preventing GA tags from firing (mentioned below).
2) Is there a way to AVOID session tracking if the app is launched in background?
The main way is to prevent the tag from firing when the app is opened in the background would be to set up a "blocking trigger" that looks for an event the indicates the app was launched in the background. Then, even if datalayer events were being fired telling the tag to fire, the blocking trigger would prevent the tag from firing, and therefore prevent a session from starting.
Why dart calls my function "aFunction" after Step2? If I execute this code this text below in console:
Step2
Step1
My code:
void main()
{
...
stream.listen(aFunction);
print("Step2");
...
}
void aFunction()
{
print("Step1");
}
Thanks for help.
One of the few promises that a Dart Stream makes is that it generates no events in response to a listen call.
The events may come at a later time, but the code calling 'listen' is allowed to continue, and complete, before the first event is fired.
We originally allowed streams to fire immediately on a listen, but when we tried to program with that, it was completely impossible to control in practice.
The same is true for listening on a future, for example with 'then'. The callback will never come immediately.
Events should generally act as if they were fired by the top-level event loop, so the event handler doesn't have to worry if other code is running - other code that might not be reentrant.
That is not always the case in practice. One event handler may trigger other events through a synchronous stream controller, effectively turning one event into anoter. That requires the event handler to know what it is doing. Synchronous controllers are intended for internal use inside, e.g., a stream transformer, and using a synchronous stream controller isn't recommended in general.
So, no, you can't have the listen call immediately trigger the callback.
You can listen to a stream synchronously if you created a StreamController with the sync option enabled. Here is an example to get what you describe:
var controller = new StreamController<String>(sync: true);
var stream = controller.stream.asBroadcastStream();
stream.listen((text) => print(text));
controller.add("Step1");
print("Step2");
I'd like to handle remove control events in my app, but also would like the event can be passed on to other apps when I'm done.
I cannot find clear instructions in Apple's Remote Control Events doc section:
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html#//apple_ref/doc/uid/TP40009541-CH7-SW3
Here it says:
iOS converts commands into UIEvent objects and delivers the events to
an app. The app sends them to the first responder and, if the first
responder doesn’t handle them, they travel up the responder chain. For
more information about the responder chain, see “The Responder Chain
Follows a Specific Delivery Path.”
So I thought I'd place
[[self nextResponder] remoteControlReceivedWithEvent: receivedEvent];
at the end of my event handler method, expecting that after my handler is done, a currently playing music app, e.g., the built-in music player, would be able to receive the event.
But to my surprise, it never did.
What am I missing here?
Make sure you call the following methods to begin receiving the events.
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
Then in your application delegate you need to listen for the events.
-(void)remoteControlReceivedWithEvent:(UIEvent *)event{
if (event.type == UIEventTypeRemoteControl){
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
break;
default:
break;
}
}
}
I have a need to play a brief sound 3 seconds or so (like a count down beep) before I perform some other action in an iOS application.
The use case is as follows:
User clicks a button... the beeps play (simple beeps using AudioServicesPlaySystemSound... then the rest of the method is run.
I cannot seem to find out a way to block my method while the tones are playing.
I've tried the following:
[self performSelector:#selector(playConfirmationBeep) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
But the tones play synchronously while the rest of the method is performed.
What am I missing with the above call?
AudioServicesPlaySystemSound is asynchronous so you can't block on it. What you want to do is get the audio services to notify you when playback is finished. You can do that via AudioServicesAddSystemSoundCompletion.
It's a C-level API so things are a bit ugly, but you probably want something like:
// somewhere, a C function like...
void audioServicesSystemSoundCompleted(SystemSoundID ssID, void *clientData)
{
[(MyClass *)clientData systemSoundCompleted:ssID];
}
// meanwhile, in your class' init, probably...
AudioServicesAddSystemSoundCompletion(
soundIDAsYoullPassToAudioServicesPlaySystemSound,
NULL, // i.e. [NSRunloop mainRunLoop]
NULL, // i.e. NSDefaultRunLoopMode
audioServicesSystemSoundCompleted,
self);
// in your dealloc, to avoid a dangling pointer:
AudioServicesRemoveSystemSoundCompletion(
soundIDAsYoullPassToAudioServicesPlaySystemSound);
// somewhere in your class:
- (void)systemSoundCompleted:(SystemSoundID)sound
{
if(sound == soundIDAsYoullPassToAudioServicesPlaySystemSound)
{
NSLog(#"time to do the next thing!");
}
}
If you actually want to block the UI while the sound is playing, and assuming your class is a view controller, you should probably just disable self.view.userInteractionDisable for the relevant period. What you definitely don't want to do is block the main run loop; that'll stop important system events like low memory warnings getting through and hence potentially cause your app to be force quit. You also probably still want to obey things like device rotations.