how to stop runaway flashing NSButton / NSTimer (IOS or OSX ok) - macos

I'm trying to make an NSButton (but I could use the technique for iOS too, so any answers are welcome).
I have a button where hiding/enabling is turned on and off:
-(void)textDidChange:(NSNotification *)notification
{
timerStatus = 0;
timerTest = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(changeButtonState:)
userInfo:nil
repeats:YES];
}
-(void) changeButtonState:(id) sender {
NSLog(#"%s", __FUNCTION__);
if (timerStatus == 2) return;
if (timerStatus == 0) {
timerStatus = 1;
saveButton.enabled = YES;
saveButton.hidden = NO;
} else {
timerStatus = 0;
saveButton.enabled = NO;
saveButton.hidden = YES;
}
}
The button blinks ok, but after it's associated method is fired (in this case a save action), I want the timer to stop and the button to stop blinking. It's this last part that's giving me a headache.. Any help appreciated.
- (IBAction)saveItemNotes:(id)sender
{
NSLog(#"%s", __FUNCTION__);
<do my save stuff here>
timerStatus = 2;
[timerTest invalidate];
timerTest = nil;
}

Just invalidate your timer in that same method where you change the button state.
Better yet, don't do it this way.
Use Core Animation instead if you want to draw attention to an interface item.

Related

iOS8 keyboard confusion, why root view's hitTest method be triggered?

When touches on the keyboard area, the root view's method be triggered:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
I am very confused,anyone can help me?
In your applicationWillEnterForeground in AppDelegate, put this code.
It works for me, specially with KLCPopup
- (void)applicationWillEnterForeground:(UIApplication *)application{
// 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.
if (!IS_OS_8_OR_LATER) return;
[UIApplication.sharedApplication.windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *w, NSUInteger idx, BOOL *stop) {
if (!w.opaque && [NSStringFromClass(w.class) hasPrefix:#"UIText"]) {
// The keyboard sometimes disables interaction. This brings it back to normal.
BOOL wasHidden = w.hidden;
w.hidden = YES;
w.hidden = wasHidden;
*stop = YES;
}
}];}
This problem will appear in:
1、you changed keywindow‘s rootViewController;
2、enter background and return to the foreground;
So,restore UITextEffectsWindow can fixed it after your change everytime.
void TSRestoreKeyboardWindow(void)
{
if (!TSSystemVersionGreaterThanIOS8()) return;
[UIApplication.sharedApplication.windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *w, NSUInteger idx, BOOL *stop) {
if (!w.opaque && [NSStringFromClass(w.class) hasPrefix:#"UIText"]) {
// The keyboard sometimes disables interaction. This brings it back to normal.
BOOL wasHidden = w.hidden;
w.hidden = YES;
w.hidden = wasHidden;
*stop = YES;
}
}];
}

iBeacon didEnterRegion not firing when in background and locked

I have an iBeacon configured as:
NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:kUUID];
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID identifier:kIdentifier];
self.beaconRegion.notifyEntryStateOnDisplay = NO;
self.beaconRegion.notifyOnEntry = YES;
self.beaconRegion.notifyOnExit = YES;
When my app is closed and the device is locked, didEnterRegion is never fired:
locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
[self sendLocalNotificationForBeaconRegionHello:(CLBeaconRegion *)region];
}
- (void)sendLocalNotificationForBeaconRegionHello:(CLBeaconRegion *)region
{
UILocalNotification *notification = [UILocalNotification new];
notification.alertBody = [NSString stringWithFormat:#"Welcome - %#", region.identifier];
notification.alertAction = NSLocalizedString(#"View", nil);
notification.soundName = UILocalNotificationDefaultSoundName;
notification.fireDate = nil;
notification.hasAction = false;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
The didExitRegion does get called even if the app is closed and the phone is locked. I only get a notification when entering a region when unlocking the phone.
Any ideas what might be the problem?
Thanks
I suspect the method is getting called but the notification swallowed for some reason (although the code looks OK). Try adding a method like below to help figure out what is going on:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if(state == CLRegionStateInside) {
NSLog(#"locationManager didDetermineState INSIDE for %#", region.identifier);
}
else if(state == CLRegionStateOutside) {
NSLog(#"locationManager didDetermineState OUTSIDE for %#", region.identifier);
}
else {
NSLog(#"locationManager didDetermineState OTHER for %#", region.identifier);
}
}
It would also be useful to see your code in didExitRegion for comparison, and hear details about how you are testing enter/exit conditions (including wait times.)

Run loop doesn't proceed events

I'm testing with run loops in standard (created by XCode) App. My App has 2 buttons:
Start Loop - starts runloop in some mode (see code below);
Stop Loop - change self.stop flag to stop runloop.
`
- (IBAction)stopLoop:(id)sender
{
self.stop = YES;
}
- (IBAction)startLoop:(id)sender
{
self.stop = NO;
do
{
[[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
if (self.stop)
{
break;
}
} while (YES);
}
`
where:
1. runLoopMode is one of the predefined modes (I try each, default, event tracking, modal, connection).
2. runLoopLimitDate [NSDate distantFuture], or [NSDate distantPast], or close feature.
3. self.stop flag is installed in other method, which called by button.
That's all, my App hasn't any other code.
AFAIU, runloop mode is a set of event sources. So, if I run runloop in some mode, runloop will be proceed those event sources, whose are associated with this mode.
By default Cocoa runs runloop in default mode and all events are proceeds greatly. But when user press startLoop button, my App is freezing: .
startLoop method is never break this infinity cycle. Application doesn't send any event to me, therefore UI freezing and user can't press stopLoop button. The same problem if I run Core Foundation counterparts.
But, when I try to receive events through NSApplication (of NSWindow) method nextEventMatchingMask:untilDate:inMode:dequeue: and pass the same mode, I receive UI events.
- (IBAction)startLoop:(id)sender
{
self.stop = NO;
do
{
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES];
if (event == nil)
{
break;
}
[NSApp sendEvent:event];
if (self.stop)
{
break;
}
} while (YES);
}
There are question: "Why if I run default run loop mode, or some other, in this way, I can't receive events?"
Thanks for your advice.
You are assuming that running the NSRunLoop in the NSDefaultRunMode processes user input events such as key presses or mouse clicks. I don't think that's the case.
NSApplication fetches events from the event with nextEventMatchingMask:untilDate:inMode:dequeue:. By running the run loop like that your not really fetching any event of the event queue.
Cloud you try something like this:
- (IBAction)startLoop:(id)sender
{
do
{
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask];
[NSApp sendEvent:event];
if (self.stop)
{
break;
}
} while (YES);
}
(Haven't tested this).
What happens if you substitute the following code in your app in place of the -startLoop: method?
- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
NSLog (#"didRun? %#, runMode: %#, dateNow: %#, limitDate: %#",
didRun ? #"YES" : #"NO",
runLoopMode,
[NSDate date],
limitDate);
}
- (IBAction)startLoop:(id)sender
{
BOOL didRun = NO;
do
{
didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
[self debugLogRunLoopInfo:didRun];
if (self.stop)
{
break;
}
} while (YES);
}
The -[NSRunLoop runUntilDate:] method spins the runloop in NSDefaultRunLoopMode. Since you're wanting to experiment with runloop modes, you could try the code below.
I've implemented a -myRunLoopUntilDate:runMode: method that does what runUntilDate: is documented to do, but allows you to specify a runloop mode.
All of this is compiled in my text editor (i.e. not compiled at all), so caveat emptor.
- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
NSLog (#"didRun? %#, runMode: %#, dateNow: %#, limitDate: %#",
didRun ? #"YES" : #"NO",
runLoopMode,
[NSDate date],
limitDate);
}
- (void) myRunLoopUntilDate:(NSDate *)limitDate runMode:(NSString *)runLoopMode
{
BOOL didRun = NO;
do {
didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:limitDate];
[self debugLogRunLoopInfo:didRun];
} while (didRun && ([limitDate timeIntervalSinceNow] > 0));
}
- (IBAction)startLoop:(id)sender
{
BOOL didRun = NO;
do
{
[self myRunLoopUntilDate:runLimitDate runMode:runLoopMode];
if (self.stop)
{
break;
}
} while (YES);
}

Xcode - Change the image of a button every iteration of for-loop

I'm creating a memory game and want to change the images of four buttons each iteration in a for-loop. What actually happens is that nothing happens until the last iteration of the loop.
When the button is pressed:
- (IBAction)bNewRound:(id)sender {
for (int i = 0; i < 10; i++) {
numbers1[i] = arc4random()%4+1;
NSLog(#"%i: %i", i, numbers1[i]);
if (numbers1[i] == 1) {
[self b1On];
}
if (numbers1[i] == 2) {
[self b2On];
}
if (numbers1[i] == 3) {
[self b3On];
}
if (numbers1[i] == 4) {
[self b4On];
}
sleep(1);
}
}
The method for changing the images of the buttons:
- (void)b1On {
NSLog(#"b1 state1");
[self.button1 setBackgroundImage:[UIImage imageNamed:#"memory_green.png"] forState: UIControlStateNormal];
bool1 = FALSE;
[self b2Off];
[self b3Off];
[self b4Off];
}
- (void)b1Off {
// NSLog(#"b1 state2");
[self.button1 setBackgroundImage:[UIImage imageNamed:#"memory_grey.png"] forState: UIControlStateNormal];
bool1 = TRUE;
}
(only for button 1 but you get it)
I follow the code in the log and everything works fine, every second a new number is generated and saved in the array, the b1On-method (or b2On, b3On etc) runs but the line
[self.button1 setBackgroundImage:[UIImage imageNamed:#"memory_green.png"] forState: UIControlStateNormal];
wont change the image each iteration.
But when the for-loop is over the last (random) button in the array will change color to green.
Why aren´t the images updated for every iteration of the for-loop?
Thanks
Never ever use sleep() on the main thread
UI updates don't happen immediately. They happen at the end of the current run loop, that means after your method is done.
You could use a NSTimer. Something like this should work:
- (void)timerFired:(NSTimer *)timer {
_timerCount--;
if (_timerCount <= 0) {
[timer invalidate];
}
// do something
}
- (void)startTimer {
[_timer invalidate];
_timerCount = 10;
_timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}

XCode View Controller Not Updating in Do Loop

I have a do loop that I want to execute a command every 1 second while a SWITCH is on.
The Code works fine ONCE, when I don't have the DO LOOP.
However, as soon as I add the LOOP, none of the labels in the view controller are updated, the back button for the storyboard doesn't work, and the SWITCH will not toggle off. Essentially, the DO LOOP keeps looping, but nothing on the screen will work, nor can I back out.
I know I'm doing it wrong. But, I don't now what. Any thoughts would be appreciated.
I attached the code that gets me in trouble.
Thanks,
- (IBAction)roaming:(id)sender {
UISwitch *roamingswitch = (UISwitch *)sender;
BOOL isOn = roamingswitch.isOn;
if (isOn) {
last=[NSDate date];
while (isOn)
{
current = [NSDate date];
interval = [current timeIntervalSinceDate:last];
if (interval>10) {
TheCommand.text=#"ON";
[self Combo:sendcommand];
last=current;
}
}
}
else
{
TheCommand.text=#"OFF";
}
}
iOS and OSX are event based systems and you cannot use loops like this in the main (UI) thread to do what you want to do, otherwise you don't allow the run loop to run and events stop being processed.
See: Mac App Programming Guide section "The App’s Main Event Loop Drives Interactions".
What you need to do is set-up a timer (NSTimer) which will fire every second:
.h file:
#interface MyClass : NSView // Or whatever the base class is
{
NSTimer *_timer;
}
#end
.m file:
#implementation MyClass
- (id)initWithFrame:(NSRect)frame // Or whatever the designated initializier is for your class
{
self = [super initInitWithFrame:frame];
if (self != nil)
{
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)dealloc
{
[_timer invalidate];
// If using MRR ONLY!
[super dealloc];
}
- (void)timerFired:(NSTimer*)timer
{
if (roamingswitch.isOn)
{
TheCommand.text=#"ON";
[self Combo:sendcommand];
}
}
#end
Give your processor enough time to update your view controller and not be interrupted by other processes. I give it 0.5 second before and after the view controller update signal.
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
self.CrashingTime.text = [NSString stringWithFormat: #"Crash Time = %f ms", outputOfCrashTime];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];

Resources