XCode View Controller Not Updating in Do Loop - xcode

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]];

Related

How to play mp3 file remotely on server without blocking UI in ios

I am working on iOS app let me play mp3 file from url. It works fine once loaded.The problem is it takes about 30 seconds to load and blocks UI from transition to other view controller.
code:
I am using AVAudioPlayer class and here are my settings.
(void)setupAudioPlayer:(NSString*)fName
{
// calls and pass url name to MyAudioPlayer class method
[self.audioPlayer initWithData:fName];
self.currentTimeSlider.maximumValue =
[self.audioPlayer getAudioDuration];
//init the current time display and the labels.
//if a current time was stored
//for this player then take it and update the time display
self.timeElapsed.text = #"0:00";
self.duration.text = [NSString stringWithFormat:#"-%#",
[self.audioPlayer timeFormat:
[self.audioPlayer getAudioDuration]]];
}
then call above method and pass the url as "http://www.test.com/test1.mp3"
(void)viewDidLoad
{
[super viewDidLoad];
self.audioPlayer = [[MyAudioPlayer alloc] init];
//pass the url where mp3 located i.e http://www.test.com/test.mp3
[self setupAudioPlayer:self.urlStr];
}
then, it play when button below pressed.
- (IBAction)playAudioPressed:(id)playButton
{
[self.timer invalidate];
//play audio for the first time or if pause was pressed
if (!self.isPaused) {
//start a timer to update the time label display
self.timer =
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateTimer:)
userInfo:nil
repeats:YES];
[self.audioPlayer playAudio];
self.isPaused = TRUE;
} else {
[self.audioPlayer pauseAudio];
self.isPaused = FALSE;
}
}
As I mentioned earlier the problem is loading for very long and blocking UI. Please help or suggest me with how to decrease loading time and avoid blocking ui.
any help will be very appreciated.
Thank you
Here is my code for the STREAMING of a remote mp3 file:
#import AVFoundation;
...
#property (nonatomic, retain) AVPlayer *objAVPlayer;
#property (nonatomic, getter=isPlaying) BOOL playing;
// in viewDidLoad
_objAVPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://www.asdasd.com/file.mp3"]];
...
- (void)playPressed {
if([self isPlaying]) {
_playing = false;
[_objAVPlayer pause];
} else {
_playing = true;
[_objAVPlayer play];
}
}
A possible time delay can be due to a slow connection. I hope this can help you.

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);
}

OSX - cannot set an NSTextField with a string from an NSTimer

I cannot seem to set a timer output to an NSTextField. I've looked at this Q/A, among many, but must be doing something different but wrong.
I initialize the timer and the textfield in WindowDidLoad.
My timer method is as follows:
time += 1;
if((time % 60) <= 9) {
NSString *tempString = [NSString stringWithFormat:#"%d:0%d",(time/60),(time%60)];
[timerTextField setStringValue:tempString];
} else {
NSString *tempString = [NSString stringWithFormat:#"%d:%d",(time/60),(time%60)];
timerTextField.stringValue = tempString;
}
NSLog(#"timerTextField: %#", timerTextField.stringValue);
As you can see, I log the textfield output.
I have an IBOutlet connection from the the File's owner to the timerTextField.
I can see the output correctly in the log, but the textfield string is not populated.
Can anyone see anything I'm doing wrong? Thanks
The code you posted looks OK to me. (You could replace it by binding your timerTextField to the time value via Cocoa Bindings though).
How did you create your timer and to which runloop(mode) did you add it?
Another problem could be that the outlet to your timerTextField is not connected. Can you set a breakpoint in your timer selector and check if timerTextField is not nil.
Update:
I just tried your code and for me it works.
Given that you have an IBOutlet named timerTextField connected to a NSTextField instance in Interface Builder, this code updates the textfield each second:
#interface SSWAppDelegate ()
{
NSUInteger time;
}
#property (weak) IBOutlet NSTextField *timerTextField;
#end
#implementation SSWAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)updateTime:(id)sender
{
time += 1;
if((time % 60) <= 9) {
NSString *tempString = [NSString stringWithFormat:#"%lu:0%lu",(time/60),(time%60)];
[self.timerTextField setStringValue:tempString];
} else {
NSString *tempString = [NSString stringWithFormat:#"%lu:%lu",(time/60),(time%60)];
self.timerTextField.stringValue = tempString;
}
NSLog(#"timerTextField: %#", self.timerTextField.stringValue);
}
#end

Subclassing NSButton, need to make it look like a regular button

I'm subclassing NSButton because I need to repeat a selector while the mouse is being held down.
I'm doing that like this:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setBezelStyle:NSBezelBorder];
PotRightIsDown = NO;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (void)mouseDown:(NSEvent *)theEvent;
{
NSLog(#"pot right mouse down");
PotRightIsDown = YES;
holdDownTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(sendCommand) userInfo:nil repeats:YES];
}
- (void)mouseUp:(NSEvent *)theEvent;
{
NSLog(#"pot right mouse up");
PotRightIsDown = NO;
}
-(void)sendCommand
{
if (PotRightIsDown)
{
NSLog(#"run the stuff here");
}
else
{
[holdDownTimer invalidate];
}
}
Works like a champ, sends the command every 100ms.
In the window in IB, I've dragged a Bevel Button onto the window and set it's class to this subclass. When I ran the application, the button is invisible however it works. I'm guessing this is because I have an empty drawRect function in the subclass.
How can I make this subclassed button look like a Bevel button?
Thank you,
Stateful
If you aren't adding any functionality to a particular subclass method then you can simply avoid implementing it altogether, which will allow the superclass to provide the default behaviour.
Alternatively (as pointed out my #Carl Norum) you can explicitly do that using:
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
}
But it's a bit pointless.

NSOpenGLView, NSWindow & NSResponder - makeFirstResponder not working

In the code below I am Initialising a NSViewController [a NSResponder], with a NSWindow, a NSOpenGLView, presenting the view and attempting to set the NSViewController as the windows first responder.
It is not working. I was expecting to be able to hit a breakpoint in the keyUp: and keyDown: methods also below, but nothing is happening.
Am I missing something?
-(void)initwithFrame:(CGRect)frame
{
window = [[MyNSWindow alloc] initWithContentRect:frame styleMask:NSClosableWindowMask | NSTitledWindowMask backing:NSBackingStoreBuffered defer: YES ];
OpenGLView* glView = [[[OpenGLView alloc] initWithFrame:window.frame] autorelease];
window.contentView = glView;
[window makeFirstResponder:self];
[window makeKeyWindow];
[window display];
}
-(void)keyDown:(NSEvent*)theEvent
{
unichar unicodeKey = [ [ theEvent characters ] characterAtIndex:0 ];
unicodeKey = 0;
}
-(void)keyUp:(NSEvent *)theEvent
{
unichar unicodeKey = [ [ theEvent characters ] characterAtIndex:0 ];
unicodeKey = 0;
}
Returning to this issue, actually the problem is elsewhere.
I had been using this sleep function to control the apps frame rate:
void System::Sleep(double seconds)
{
NSDate* limit = [NSDate dateWithTimeIntervalSinceNow:seconds];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop runUntilDate:limit];
}
Doing this seems to freeze the system completely and block the key events.
Instead use this to schedule the update function:
[NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(updateApp:) userInfo:nil repeats:YES];
For instances to participate in key-view loops, a custom view must return YES from acceptsFirstResponder.
I had this problem also. This thread might help
keyDown not being called
I found out what was preventing the keyDown event from being called.
It was the NSBorderlessWindowMask mask, it prevents the window from
become the key and main window. So I have created a subclass of
NSWindow called BorderlessWindow:
#interface BorderlessWindow : NSWindow { }
#end
#implementation BorderlessWindow
- (BOOL)canBecomeKeyWindow {
return YES; }
- (BOOL)canBecomeMainWindow {
return YES; }
#end

Resources