Could somebody give me guidance/starting point on how I would update a UI Text Label in an IOS app from a library.
I have temperature data being received from a BT module in a library connected to my app. I want to send that Integer data to my app and update the UI Text label.
NOTE: I have full access to the library.
Any help is appreciated
I think what you are asking for is a way to provide feedback to an application from your Cocoa static library.
I would suggest that you have a look at the NSNotificationCenter class. For example, say that you have a class BTThermometer that, upon the reception of a new measurement calls:
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.my.BTThermometer.NewValue" object:self];
Then, in you application, you could do something like:
[[NSNotificationCenter defaultCenter] addObserverForName:#"com.my.BTThermometer.NewValue" object:self queue:nil usingBlock:^(NSNotification* n) {
dispatch_async(dispatch_get_main_queue(), ^{
someLabel.text = ((BTThermometer*)n.object).temperatureValue;
}
}];
This is a standard mechanism in iOS to decouple your application from the internal workings of your library. The only coupling is the name itself, and of course it is usually a good idea to use a constant (e.g. #define kMyTemperatureEvent #"com.my.BTThermometer.NewValue) so that the compiler catches any typos.
Related
I've implemented iCloud sync of a single document.
The stand is always actual, when the app starts, but changes made in parallel on other device are never (I saw it once, not sure if I didn't dream that time) propagated when the app is running.
I've implemented
_metadataQuery = [[NSMetadataQuery alloc] init];
_metadataQuery.searchScopes = #[NSMetadataQueryUbiquitousDataScope];
_metadataQuery.predicate = [NSPredicate predicateWithFormat:#"%K like %#", NSMetadataItemFSNameKey, fileName];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(metadataQueryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:_metadataQuery];
[_metadataQuery startQuery];
and it's get called, e.g. when I save the document (so principally it works).
And also
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentStateChanged:) name:UIDocumentStateChangedNotification object:self];
for the subclassed UIDocument.
In Xcode I see some download activities for my app after I do changes on another device... (but it never show any upload activities, even if I see on another device data getting to be actualized)
But corresponding methods don't get called.
If I do cold restart of the App - everything is fine (I get the "new" data).
Any ideas what could be missing?
Note: may be it's somehow relevant - I'm closing the document right after reading, so the document is typically in state "closed"...
It seems I've reused wrong example, had to use
_metadataQuery.searchScopes = #[NSMetadataQueryUbiquitousDocumentsScope];
I am trying to update a view when something happens in another class, and after some looking, it appeared that the most common way to do this was to use either delegates or blocks to create a callback. However, I was able to accomplish this task using notifications. What I want to know is: Is there a problem using notifications to trigger methods calls? Are there any risks I'm not aware of? Is there a reason I'd want to use blocks/delegates over notifications?
I'm new to Objective-C, so I'm not sure if the approach I'm taking is correct.
As an example, I'm trying to set the battery level of a BLE device on the ViewController. I have a BluetoothLEManager, which discovers the peripheral, its services/characteristics, etc. But to do this, I need to initiate the "connection" in the detailViewController, then update the battery level once I find it.
Here is some example code of what I'm doing:
DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"Selected tag UUID: %#", [selectedTag.tagUUID UUIDString]);
tagName.text = selectedTag.mtagName;
if(selectedTag.batteryLevel != nil){
batteryLife.text = selectedTag.batteryLevel;
}
uuidLabel.text = [selectedTag.tagUUID UUIDString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setBatteryLevel:) name:#"SetBatteryLevel" object:nil];
}
...
-(void)setBatteryLevel:(NSNotification*)notif{
NSMutableString* batLevel = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:#"%#", selectedTag.batteryLevel]];
[batLevel appendString:#" %"];
selectedTag.batteryLevel = batLevel;
batteryLife.text = selectedTag.batteryLevel;
}
BluetoothLEManager.m:
...
-(void) getBatteryLevel:(CBCharacteristic *)characteristic error:(NSError *)error fetchTag:(FetchTag *)fetchTag
{
NSLog(#"Getting battery Level...");
NSData* data = characteristic.value;
const uint8_t* reportData = [data bytes];
uint16_t batteryLevel = reportData[0];
selectedTag.batteryLevel = [NSString stringWithFormat:#"%i", batteryLevel];
NSLog(#"Battery Level is %#", [NSString stringWithFormat:#"%i", batteryLevel]);
[[NSNotificationCenter defaultCenter] postNotificationName:#"SetBatteryLevel" object:nil];
}
...
Let me know if you need any other code, but this is the basics of it all.
Each approach has different strengths and weaknesses.
Delegates and protocols require a defined interface between the object and it's delegate, a one-to-one relationship, and that the object have specific knowledge of the delegate object it's going to call.
Methods with completion blocks involve a similar one-to-one relationship between an object and the object that invokes the method. However since blocks inherit the scope in which they're defined, you have more flexibility as to the context that's available in the completion block. Blocks also allow the caller to define the completion code in same place that the call takes place, making you code more self-documenting.
In both cases, the object that is notifying the delegate or invoking the completion block has to know who it's talking to, or what code is being executed.
A delegate call is like an auto shop calling you back to let you know your car is done. The service manager has to have your phone number and know that you want a call.
A block is more like a recipe you give to a chef. Give the chef a different recipe and he/she performs a different task for you.
Notifications are much less tightly coupled. It's like a town crier, yelling announcements in a crowded public square. The crier doesn't need to know who's listening, or how many people are listening.
Likewise, when you send a notification, you don't know who, if anybody, is listening, or how many listeners there are. You don't need to know. If 10 objects care about the message you are broadcasting, they can all listen for it, and they'll all be notified. The message sender doesn't have to know or care who's listening.
Sometimes you want tighter coupling, and sometimes you want looser coupling. It depends on the problem you're trying to solve.
When game controller be paired from system setting, all is fine.
But I want to discover & pair game controller in my app.
Actually I found that it seems feasible by Apple's docs.
doc link : Discovering and Connecting to Controllers.
I have a game controller which is in pairing...
But I found the log of function "startWirelessControllerDiscoveryWithCompletionHandler" never be shown.
Seems the behavior does not conform.
I call "startWirelessControllerDiscoveryWithCompletionHandler" when app load...
I also call "stopWirelessControllerDiscovery", but still same.
- (void)viewDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(gameControllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(gameControllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
[GCController startWirelessControllerDiscoveryWithCompletionHandler:^{
NSLog(#"Finished finding controllers");
[self completionWirelessControllerDiscovery];
}];
...
}
- (void)completionWirelessControllerDiscovery {
if (isDebug) {
NSLog(#"%s-%d", __FUNCTION__, __LINE__);
}
}
Someone has experiences on this?
My experience has been that it does not work for pairing a controller - I have always had to pair my game controllers directly to the device. I've been developing a wrapper around GCController, so I've tested about 5 different MFi controllers with no luck getting GCController to manage the pairing process.
I've used Google Analytics on several iOS apps. No problems. This time, problem.
I do the basic setup using version 3.0. Add library/header, include required frameworks, and stuff the boiler plate code into the AppDelegate.m. So far so good, everything works as expected. I take my first UIViewController and change it to extend GAITrackedViewController and it hits the fan. The app freezes up on the first screen and memory usage starts going up about 4Meg per second. So I change the UIViewController back and all is good. I try making the screen name call manually in viewDidLoad.
// Analytics
id tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:#"Initial"];
[tracker send:[[GAIDictionaryBuilder createAppView] build]];
Same thing happens. My view controller has a couple custom container views and it the root view controller on a generic UINavigationViewController. I figure it's probably the custom containers confusing it about which is the active view controller and what screen name to use (but I'm not seeing any sign of this in the logging).
Has anyone run into this problem and been able to nail down exactly what's causing it and how to work around it?
João's answer is correct, but I'd like to explain it more.
From Google's Getting Started document
If your app uses the CoreData framework: responding to a notification,
e.g. NSManagedObjectContextDidSaveNotification, from the Google
Analytics CoreData object may result in an exception. Instead, Apple
recommends filtering CoreData notifications by specifying the managed
object context as a parameter to your listener.
What that means is...
// This code will cause a problem because it gets triggered on ANY NSManagedObjectContextDidSaveNotification.
// (both your managed object contact and the one used by Google Analytics)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
// This code is safe and will only be trigger from the notification generated by your Managed Object Context.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:myManagedObjectContext];
Now I read the docs and I had done this properly, but I was still having the problem. Turns out I didn't update my code for when I removed the notification.
// Not Safe
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:nil];
// Safe
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:myManagedObjectContext];
The moral of the story is, pay attention to your notification listeners. It takes a couple seconds to specify a listener for a specific object and it can take a long time to debug an issue because you accidentally listening to events you don't want to or remove listening to events.
Solution 1: Use a specific moc on object parameter when observing NSManagedObjectContextDidSaveNotification, this will allow you to observe only saves on the given moc.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:managedObjectContext];
Solution 2: If you are using the Core Data technique of merging mocs created on background threads, you cannot easily solve in the suggested way, so the alternative is to change your method that handles notification in order to avoid merging when the persistentStoreCoordinator of the saved moc doesn't match the persistentStoreCoordinator of your main moc.
- (void)managedObjectContextDidSave:(NSNotification *)notification {
if ([NSThread isMainThread]) {
NSManagedObjectContext *savedMoc = notification.object;
// Merge only saves of mocs that are not my managedObjectContext
if (savedMoc == self.managedObjectContext) {
return;
}
// Merge only saves of mocs that share the same persistentStoreCoordinator of my managedObjectContext (i.e.: ignore the save of Google Analytics moc)
if (savedMoc.persistentStoreCoordinator != self.managedObjectContext.persistentStoreCoordinator) {
return;
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
else {
[self performSelectorOnMainThread:#selector(handleBackgroundContextSaveNotification:) withObject:notification waitUntilDone:YES];
}
}
I was having the exact same problem.
I've managed to find the solution in my case: I was registering to NSManagedObjectContextDidSaveNotification without specifying the context:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
Removing this listener solved my out of memory problems.
Cheers
Im working on XMPP Framwork and as soon i fixed GA crash. This one failed https://github.com/robbiehanson/XMPPFramework/blob/master/Extensions/CoreDataStorage/XMPPCoreDataStorage.m
ANy easy way to solve this issue.
https://github.com/robbiehanson/XMPPFramework/issues/577
In a typical layered architecture, how can I effectively communicate errors originating in data access layer to a UIViewContoller?
I have following design:
UIViewControllers --> datacontroller --> specificserviceproxy --> serviceproxybase
serviceproxybase initiates calls to a web service. I check for network availability before invoking operations on the web service, and want to alert users in case of network breakdown.
What is the best practice solution? Thanks.
Follow the pattern used my much of Cocoa-touch. Many methods return nil or NO when there is an error and such methods have an NSError out parameter with details about the error.
You can propagate the result and error up the layers as needed. Or a layer may wrap the error with a more layer specific error and pass up the new error.
Example methods that follow this pattern are:
NSFileManager copyItemAtPath:toPath:error:
NSFileManager attributesOfItemAtPath:error:
You can use NSNotificationCenter to post and receive NSNotifications for various error states.
In serviceproxybase you would post the notification for any interested observers:
[[NSNotificationCenter defaultCenter] postNotificationName:#"NoConnectionNotification" object:nil userInfo:someErrorInfoObject];
Typically you would package up an object in someErrorInfoObject that gives some additional details to the observer, such as an NSError object, the service request that failed, or a service's error code. In a view controller you would listen for that notification in viewDidLoad or viewWillAppear:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleNoConnectionError:)
name:#"NoConnectionNotification"
object:nil];
Now whenever a notification with the name NoConnectionNotification is posted, the view controller will receive a message to handleNoConnectionError: with the corresponding NSNotification.