I've written a function that checks whether a live score has changed every 0.1 seconds, and if it has it plays a system sound on iPhone.
- (void) checkIfShouldHaptic {
loadedScore = [self loadScoreFromKey:keyScore]; //load score saved in key
controllerScore = [self checkCurrentScore]; //check current score
if (loadedScore < controllerScore){ //if score in key is less than controller score
[self saveScore:controllerScore]; //save new score in key
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); //play system sound
[self repeatCheckIfShouldHaptic]; //repeat
}
else { //else score is not less
[self repeatCheckIfShouldHaptic]; //repeat
}
}
- (void)repeatCheckIfShouldHaptic {
[NSTimer scheduledTimerWithTimeInterval:timeDelayInSeconds target:self selector:#selector(checkIfShouldHaptic) userInfo:nil repeats:NO];
}
My programming capabilities are pretty limited so I was wondering if someone could show me if and how this could be optimised?
I'm not sure if a function calling itself over and over is good practice or if there's a better way to repeat a check.
Thank you.
I think you can use KVO
#property NSUInteger score;
- (void)viewDidLoad
{
[super viewDidLoad];
[self addObserver:self forKeyPath:#"score" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"From KVO");
if([keyPath isEqualToString:#"score"])
{
id oldScore = [change objectForKey:NSKeyValueChangeOldKey];
id newScore = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(#"%# %#", oldScore, newScore);
}
}
I would rewrite your code a little. IMHO Using NSTimer is more resources consuming operation then using GCD.
Your approach is not so bad if you don't have any other options to receive some sort of notification.
- (void)checkIfShouldHaptic {
loadedScore = [self loadScoreFromKey:keyScore]; //load score saved in key
controllerScore = [self checkCurrentScore]; //check current score
if (loadedScore < controllerScore){ //if score in key is less than controller score
[self saveScore:controllerScore]; //save new score in key
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); //play system sound
}
[self repeatCheckIfShouldHaptic]; //repeat
}
- (void)repeatCheckIfShouldHaptic {
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeDelayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf checkIfShouldHaptic]
});
}
Related
I'm not familiar with iOS but I'm trying to find when the default, built-in camera application is focusing. To do this I create my own separate Objective-C application and following this answer here [iPhone : camera autofocus observer? but I'm not getting anything from observeValueForKeyPath in the NSLog.
#import "ViewController.h"
#import "AVFoundation/AVCaptureDevice.h"
#import "AVFoundation/AVMediaFormat.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(#"viewDidLoad");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// callback
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"observeValueForKeyPath");
if( [keyPath isEqualToString:#"adjustingFocus"] ){
BOOL adjustingFocus = [ [change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1] ];
NSLog(#"Is adjusting focus? %#", adjustingFocus ? #"YES" : #"NO" );
NSLog(#"Change dictionary: %#", change);
}
if( [keyPath isEqualToString:#"focusMode"] ){
AVCaptureFocusMode focusMode = [ [change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1] ];
NSLog(#"focusMode? %ld", focusMode);
}
}
// register observer
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear: animated];
NSLog(#"viewWillAppear");
AVCaptureDevice *camDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
int flags = NSKeyValueObservingOptionNew;
[camDevice addObserver:self forKeyPath:#"adjustingFocus" options:flags context:nil];
[camDevice addObserver:self forKeyPath:#"focusMode" options:flags context:nil];
}
#end
Any help much appreciated.
For anyone who visits this question, the answer is what Bluewings wrote as a comment. I was trying to use KVO to observe one application from another which is not possible since only one lock on a capture device is possible at one time.
I have an image view that has two animation images, occuring in 1-second intervals. I want to run some methods when my image view is displaying one of those two images
I already tried doing:
if(self.imageViewThatPerformsAnimation.image == [UIImage imageNamed: #"someImage"])
[self doSomeMethod];
but when I tried this and ran it, [self doSomeMethod]; always ran, and not just when the image view was displaying that one image.
I'm thinking about having a timer that changes a boolean value every one second then saying
if (booleanValue==YES)
[self doSomeMethod]
It's just that I feel there may be a better way.
If you wanted to use a NSTimer, it might look like:
#interface MyViewController ()
{
NSTimer *_timer;
NSArray *_images;
NSInteger _currentImageIndex;
}
#end
#implementation MyViewController
#synthesize imageview = _imageview;
- (void)viewDidLoad
{
[super viewDidLoad];
_images = [NSArray arrayWithObjects:
[UIImage imageNamed:#"imgres-1.jpg"],
[UIImage imageNamed:#"imgres-2.jpg"],
[UIImage imageNamed:#"imgres-3.jpg"],
[UIImage imageNamed:#"imgres-4.jpg"],
nil];
_currentImageIndex = -1;
[self changeImage];
// Do any additional setup after loading the view.
}
- (void)changeImage
{
_currentImageIndex++;
if (_currentImageIndex >= [_images count])
_currentImageIndex = 0;
self.imageview.image = [_images objectAtIndex:_currentImageIndex];
if (_currentImageIndex == 0)
[self doSomething];
}
- (void)startTimer
{
if (_timer) {
[_timer invalidate];
_timer = nil;
}
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(changeImage)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
}
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startTimer];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self stopTimer];
}
I have an AVQueuePlayer (which obviously extends AVPlayer) that loads a playlist of streaming audio. Streaming is all working fine, but I'd like to have an activity indicator to show the user audio is loading. Trouble is, I can't seem to find any such Notification in AVQueuePlayer (or AVPlayer) that would indicate when the audio buffer has finished loading/is ready to play (nor does there appear to be a delegate method). Any thoughts?
You will have to use KVO to get this done.
For each item you are adding to the queue, you may setup observers like this:
item_ = [[AVPlayerItem playerItemWithURL:[NSURL URLWithString:#"http://somefunkyurl"]] retain];
[item_ addObserver:self forKeyPath:#"status" options:0 context:nil];
[item_ addObserver:self forKeyPath:#"playbackBufferEmpty" options:0 context:nil];
Now you can evaluate the status of that item within the observer method;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([object isKindOfClass:[AVPlayerItem class]])
{
AVPlayerItem *item = (AVPlayerItem *)object;
//playerItem status value changed?
if ([keyPath isEqualToString:#"status"])
{ //yes->check it...
switch(item.status)
{
case AVPlayerItemStatusFailed:
NSLog(#"player item status failed");
break;
case AVPlayerItemStatusReadyToPlay:
NSLog(#"player item status is ready to play");
break;
case AVPlayerItemStatusUnknown:
NSLog(#"player item status is unknown");
break;
}
}
else if ([keyPath isEqualToString:#"playbackBufferEmpty"])
{
if (item.playbackBufferEmpty)
{
NSLog(#"player item playback buffer is empty");
}
}
}
}
I have a lot of binary data on Core Data which I would like to remove and save as files so I setup the code so that it would run a manual migration.
The problem is that because I have to pull binary from Core Data and save them to files, the migration process could take a very long time depending on how much data there is.
I would like to show a simple progress label so that users wouldn't think that the app is frozen.
// app did launch
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// add the progress label
self.progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(103, 36, 114, 21)];
[self.progressLabel setText:#"0%"];
[self.window addSubview:self.progressLabel];
[self.window makeKeyAndVisible];
...
// start migration
[self progressivelyMigrateURL:storeURL];
...
}
// manually migrate store
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL
{
NSLog(#"start progressivelyMigrateURL");
...
// create the migration manager
NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel];
...
// register for KVO of migration progress here
[manager addObserver:self forKeyPath:#"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL];
// start the migration
if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error])
{
return NO;
}
...
}
// handle KVO of migration process here
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqual:#"migrationProgress"])
{
float migrationProgress = [[change objectForKey:NSKeyValueChangeNewKey] floatValue];
int percent = migrationProgress * 100;
[self.progressLabel setText:[NSString stringWithFormat:#"%d%", percent]];
[self.progressLabel layoutIfNeeded];
[self.progressLabel setNeedsDisplay];
NSLog(#"migrationProgress: %d", percent);
NSLog(#"self.testLabel text: %#", self.testLabel.text);
}
}
The migration runs perfectly and I can confirm that the NSLog is returning incrementing values while the migration is taking place.
The problem is that self.progressLabel does not redraw every time the progress is updated and just skips from 0% to 100% while the migration is taking place. I know I can't run the migration on a separate thread. How can I make this work?
You precisely need to perform the migration on a background thread, and update the UI as you get progress notifications on the main thread.
- (void)startMigration {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSMigrationManager * manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:destinationModel];
[manager addObserver:self forKeyPath:#"migrationProgress" options:0 context:NULL];
BOOL success = [manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error];
[manager removeObserver:self forKeyPath:#"migrationProgress"];
if(success) {
dispatch_async(dispatch_get_main_queue(), ^{
// Go back to the main thread and continue…
});
}
}
And when getting notifications:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
dispatch_sync(dispatch_get_main_queue(), ^{
CGFloat progress = [(NSMigrationManager *)object migrationProgress];
// Update UI progress indicator
});
}
I have now nearly figured out how to Filter a NSTreeController, to do this I have sub-classed NSManagedObject and added some code to my App Delegate, I have also bound my NSSearchField to the filterPredicate of my App Delegate but I think I need to connect my NSTreeController and NSSearchField in some way to make it work.
Below I have posted all the code I have used so far to try and make it work.
NSManagedObject Sub-Class Header File.
#interface Managed_Object_Sub_Class : NSManagedObject {
NSArray *filteredChildren; // this should fix the compiler error
}
- (NSArray *)filteredChildren;
#end
NSManagedObject Sub-Class Implementation File.
#implementation Managed_Object_Sub_Class
static char *FilteredChildrenObservationContext;
- (id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context {
if (self = [super initWithEntity:entity insertIntoManagedObjectContext:context]) {
[[NSApp delegate] addObserver:self forKeyPath:#"filterPredicate" options:0 context:&FilteredChildrenObservationContext];
[self addObserver:self forKeyPath:#"subGroup" options:0 context:&FilteredChildrenObservationContext];
}
return self;
}
// use finalize with GC
- (void)dealloc {
[[NSApp delegate] removeObserver:self forKeyPath:#"filterPredicate"];
[self removeObserver:self forKeyPath:#"subGroup"];
[super dealloc];
}
- (NSArray *)filteredChildren {
if (filteredChildren == nil) {
NSPredicate *predicate = [[NSApp delegate] filterPredicate];
if (predicate)
filteredChildren = [[[self valueForKey:#"subGroup"] filteredArrayUsingPredicate:predicate] copy];
else
filteredChildren = [[self valueForKey:#"subGroup"] copy];
}
return filteredChildren;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &FilteredChildrenObservationContext) {
[self willChangeValueForKey:#"filteredChildren"];
[filteredChildren release];
filteredChildren = nil;
[self didChangeValueForKey:#"filteredChildren"];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#end
Code Added To App Delegate Header File
NSPredicate *filterPredicate;
Code Added To App Delegate Implementation File
- (NSPredicate *)filterPredicate {
return filterPredicate;
}
- (void)setFilterPredicate:(NSPredicate *)newFilterPredicate {
if (filterPredicate != newFilterPredicate) {
[filterPredicate release];
filterPredicate = [newFilterPredicate retain];
}
}
Search Field Binding
alt text http://snapplr.com/snap/vs9q
This doesn't work yet, and so that is why I am asking what I need to do from here to make it work, like I said I think I need to connect the NSSearchField and NSTreeController Together in some way.
Again I hav answered my own question, I also hope that this will help other people so they know how to Filter a NSTreeController.
To make it work from my post above do the following.
1.For your entity set the Class as your NSManagedObject Sub-Class in my Case JGManagedObject.
alt text http://dvlp.me/c3k
2.For your search field in IB set the predicate format to what you want to Filter ( The Property in your entity, for me it is name).
alt text http://dvlp.me/9k9rw