In my app if I tap one button I need to display all the phone contacts like whatsapp... How to acheive this with AddressBook framework? Or can we use any other frameworks to display all the device contacts...
We can fetch the contact using the contact framework: following the step->
Add the contact.framework and contactUI.framework in your app bundle.
Add the two file in your .h file:
#import Contacts/Contacts.h
#import ContactsUI/ContactsUI.h
Add the flowing code
-(void)loadContactList
{
#try {
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if( status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted)
{
NSLog(#"access denied");
}
else
{
//Create repository objects contacts
CNContactStore *contactStore = [[CNContactStore alloc] init];
//Select the contact you want to import the key attribute ( https://developer.apple.com/library/watchos/documentation/Contacts/Reference/CNContact_Class/index.html#//apple_ref/doc/constant_group/Metadata_Keys )
NSArray *keys = [[NSArray alloc]initWithObjects:CNContactIdentifierKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactPhoneNumbersKey,CNContactViewController.descriptorForRequiredKeys,nil];
// Create a request object
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
request.predicate = nil;
[contactStore enumerateContactsWithFetchRequest:request
error:nil
usingBlock:^(CNContact* __nonnull contact, BOOL* __nonnull stop)
{
// Contact one each function block is executed whenever you get
NSString *phoneNumber = #"";
if( contact.phoneNumbers)
phoneNumber = [[[contact.phoneNumbers firstObject] value] stringValue];
NSLog(#"phoneNumber = %#", phoneNumber);
NSLog(#"givenName = %#", contact.givenName);
NSLog(#"familyName = %#", contact.familyName);
NSLog(#"email = %#", contact.emailAddresses);
[contactList addObject:contact];
}];
}
} #catch (NSException *exception) {
NSLog(#"Exception:%#",exception.reason);
}
}
Call this method in view did load or view did appear.
Related
I am using QuickBlox-iOS SDK for chatting. Login/Signup is working perfectly. Also I am able to send message but the delegate method
- (void)chatDidReceiveMessage:(QBChatMessage *)message;
is not getting called. Here's the code I am using to setup chat. Adding the following code in appDelegate :
// connect to Chat
[[QBChat instance] addDelegate:self];
QBUUser *currentUser = [QBUUser user];
currentUser.ID = [Global sharedInstance].currentUser.ID;
currentUser.password = #"password";
[[QBChat instance] connectWithUser:currentUser completion:^(NSError * _Nullable error) {
NSLog(#"connect to chat error %#",error);
}];
And the below code I am using to send message :
QBChatMessage *message = [QBChatMessage message];
message.recipientID=[Global sharedInstance].QBUserID;
message.senderID=[Global sharedInstance].currentUser.ID;
[message setText:messageTextView.text];
message.dateSent = [NSDate date];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[#"save_to_history"] = #YES;
[message setCustomParameters:params];
[QBRequest createMessage:message successBlock:^(QBResponse *response, QBChatMessage *createdMessage) {
NSLog(#"success: %#", createdMessage);
} errorBlock:^(QBResponse *response) {
NSLog(#"ERROR: %#", response.error);
}]
I checked on QuickBlox dashboard. It shows all the sent/received messages. But the delegate is not getting called when I send message to another user. I am not using any additional services classes (QMServices) like they are using in their Example Project. Any help would be appreciated. Thanks
I don't understand why you're using the [QBRequest createMessage:successBlock:errorBlock:] method to send messages to another user.
For me what always worked was to create a chatDialog with the user you're trying to message, like so:
QBChatDialog *dialog = [[QBChatDialog alloc] initWithDialogID:nil
type: QBChatDialogTypePrivate];
dialog.occupantIDs = #[#([Global instance].QBUserID),
#([Global instance].currentUser.user.ID)];
Afterwards, you can call Quickblox method to create the dialog on the servers:
if (dialog.ID == nil) {
[QBRequest createDialog:dialog successBlock:^(QBResponse *response, QBChatDialog *createdDialog) {
[self sendMessageToDialog: dialog withText:#"Hello friend!"];
} errorBlock:^(QBResponse *response) {
NSLog(#"dialog creation err: %#", response);
}];
}
Create the message:
- (QBChatMessage *) createMessageWithText: (NSString *)text andDialog: (QBChatDialog*)dialog {
QBChatMessage *message = [QBChatMessage message];
message.text = text;
message.senderID = [Global instance].currentUser.ID;
message.markable = YES;
message.deliveredIDs = #[#([Global instance].currentUser.ID)];
message.readIDs = #[#([Global instance].currentUser.ID)];
message.dialogID = dialog.ID;
message.dateSent = [NSDate date];
message.recipientID = dialog.recipientID;
message.customParameters = [NSMutableDictionary dictionary];
message.customParameters[kQMCustomParameterDialogID] = dialog.ID;
message.customParameters[kQMCustomParameterDialogType] = [NSString stringWithFormat:#"%lu",(unsigned long)dialog.type];
message.customParameters[#"application_id"] = #"<your-application-id>";
message.customParameters[#"save_to_history"] = #"1";
if (dialog.lastMessageDate != nil){
NSNumber *lastMessageDate = #((NSUInteger)[dialog.lastMessageDate timeIntervalSince1970]);
message.customParameters[kQMCustomParameterDialogRoomLastMessageDate] = [lastMessageDate stringValue];
}
if (dialog.updatedAt != nil) {
NSNumber *updatedAt = #((NSUInteger)[dialog.updatedAt timeIntervalSince1970]);
message.customParameters[kQMCustomParameterDialogRoomUpdatedDate] = [updatedAt stringValue];
}
return message;
}
And then send the message to the dialog:
- (void) sendMessageToDialog: (QBChatDialog *)dialog withText: (NSString *)text {
QBChatMessage *message = [[ChatService shared] createMessageWithText:text andDialog:self.dialog];
[dialog sendMessage:message completionBlock:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"error creating message %#", error);
} else {
NSLog(#"message sent!");
}
}];
}
I think following this flux you'll be able to receive the callback through the delegate.
EDIT - I forgot to mention the consts I used in the code above are:
NSString const *kQMCustomParameterDialogID = #"dialog_id";
NSString const *kQMCustomParameterDialogRoomName = #"room_name";
NSString const *kQMCustomParameterDialogRoomPhoto = #"room_photo";
NSString const *kQMCustomParameterDialogRoomLastMessageDate = #"room_last_message_date";
NSString const *kQMCustomParameterDialogUpdatedDate = #"dialog_updated_date";
NSString const *kQMCustomParameterDialogType = #"type";
NSString const *kQMCustomParameterDialogRoomUpdatedDate = #"room_updated_date";
Have you added <QBChatDelegate> into your .h file.
Is there an example code for corespotlight search feature - iOS 9 API? Really appreciate if can look at sample code to implement/test.
Create a new iOS project and add CoreSpotlight and MobileCoreServices framework to your project.
Create the actual CSSearchableItem and associating the uniqueIdentifier, domainIdentifier and the attributeSet. Finally index the CSSearchableItem using [[CSSearchableIndex defaultSearchableIndex]...] as show below.
OK!Test the index!
CSSearchableItemAttributeSet *attributeSet;
attributeSet = [[CSSearchableItemAttributeSet alloc]
initWithItemContentType:(NSString *)kUTTypeImage];
attributeSet.title = #"My First Spotlight Search";
attributeSet.contentDescription = #"This is my first spotlight Search";
attributeSet.keywords = #[#"Hello", #"Welcome",#"Spotlight"];
UIImage *image = [UIImage imageNamed:#"searchIcon.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc]
initWithUniqueIdentifier:#"com.deeplink"
domainIdentifier:#"spotlight.sample"
attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item]
completionHandler: ^(NSError * __nullable error) {
if (!error)
NSLog(#"Search item indexed");
}];
Note: kUTTypeImage requires that you import the MobileCoreServices framework.
To complete the spotlight search functionality, once you have implemented mayqiyue's answer, you'll be able to see the results in the search but on selection of the result simply your app would open not the related view with related content.
In order to do so, go to your AppDelegate.m and add the following method.
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
//check if your activity has type search action(i.e. coming from spotlight search)
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType ] == YES) {
//the identifier you'll use to open specific views and the content in those views.
NSString * identifierPath = [NSString stringWithFormat:#"%#",[userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier]];
if (identifierPath != nil) {
// go to YOUR VIEWCONTROLLER
// use notifications or whatever you want to do so
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
MyViewController *myViewController = [storyboard instantiateViewControllerWithIdentifier:#"MyViewController"];
// this notification must be registered in MyViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"OpenMyViewController" object: myViewController userInfo:nil];
return YES;
}
}
return NO;
}
Make sure to import in AppDelegate.m :
#import <MobileCoreServices/MobileCoreServices.h>
#import <CoreSpotlight/CoreSpotlight.h>
UPDATE for Swift 2.1
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if #available(iOS 9.0, *) {
if userActivity.activityType == CSSearchableItemActionType {
//the identifier you'll use to open specific views and the content in those views.
let dict = userActivity.userInfo! as NSDictionary
let identifierPath = dict.objectForKey(CSSearchableItemActivityIdentifier) as! String
if identifierPath.characters.count > 0 {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let mvc: MyViewController = storyboard.instantiateViewControllerWithIdentifier("MyViewController") as! MyViewController
NSNotificationCenter.defaultCenter().postNotificationName("OpenMyViewController", object: mvc, userInfo: nil)
}
return true
}
} else {
// Fallback on earlier versions
return false
}
return false
}
Make sure to import in AppDelegate.swift :
import CoreSpotlight
import MobileCoreServices
I am using similar implementation as mentioned by #mayqiyue but I am also checking the existence of the item variable for backwards compatibility with iOS 8.
- (void)setupCoreSpotlightSearch
{
CSSearchableItemAttributeSet *attibuteSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(__bridge NSString *)kUTTypeImage];
attibuteSet.title = NSLocalizedString(#"Be happy!", #"Be happy!");
attibuteSet.contentDescription = #"Just like that";
attibuteSet.keywords = #[#"example", #"stackoverflow", #"beer"];
UIImage *image = [UIImage imageNamed:#"Image"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attibuteSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"1"
domainIdentifier:#"album-1"
attributeSet:attibuteSet];
if (item) {
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Search item indexed");
}
}];
}
}
To handle the tap on the search item from Spotlight you need to implement following method in your AppDelegate:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) {
NSString *uniqueIdentifier = userActivity.userInfo[CSSearchableItemActivityIdentifier];
// Handle 'uniqueIdentifier'
NSLog(#"uniqueIdentifier: %#", uniqueIdentifier);
}
return YES;
}
Write in your main controller Class
-(void)storeValueForSpotligtSearch {
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
// **Your Model Array that Contain Data Like attributes Make, Model, Variant and Year and Images**
for (MyCatalogeModel *myCatalogeModelObj in yourDataContainer) {
NSMutableArray *arrKeywords = [[NSMutableArray alloc] initWithObjects: myCatalogeModelObj.year, myCatalogeModelObj.make, myCatalogeModelObj.model, myCatalogeModelObj.variant, nil];
NSString *strIdentifier = [NSString stringWithFormat:#"%#.%#",bundleIdentifier, myCatalogeModelObj.carId];
self.userActivity = [[NSUserActivity alloc]initWithActivityType:strIdentifier];
self.userActivity.title = myCatalogeModelObj.year;
self.userActivity.title = myCatalogeModelObj.make;
self.userActivity.title = myCatalogeModelObj.model;
self.userActivity.title = myCatalogeModelObj.variant;
self.userActivity.eligibleForSearch = YES;
self.userActivity.eligibleForPublicIndexing = YES;
self.userActivity.eligibleForHandoff = YES;
CSSearchableItemAttributeSet * attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeJSON];
attributeSet.title = myCatalogeModelObj.make;
attributeSet.thumbnailData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[myCatalogeModelObj.imageArray objectAtIndex:0]]];
attributeSet.contentDescription = [NSString stringWithFormat:#"%# %# %# %#", myCatalogeModelObj.year, myCatalogeModelObj.make, myCatalogeModelObj.model, myCatalogeModelObj.variant];
attributeSet.keywords = arrKeywords;
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:strIdentifier domainIdentifier:#"spotlight.CARS24ChannelPartnerapp" attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
}];
self.userActivity.contentAttributeSet = attributeSet;
[self.userActivity becomeCurrent];
[self updateUserActivityState:self.userActivity];
}
}
Write in App Delegate
-(BOOL)application:(nonnull UIApplication *) application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * __nullable))restorationHandler {
#try {
NSString *strIdentifier;
NSNumber *numScreenId;
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
NSLog(#"Activity = %#",userActivity.userInfo);
if (userActivity.userInfo[#"vc"]) {
numScreenId = userActivity.userInfo[#"vc"];
}
else{
strIdentifier = [userActivity.userInfo objectForKey:#"kCSSearchableItemActivityIdentifier"];
NSLog(#"strIdentifier : %#",strIdentifier);
NSArray *arr = [strIdentifier componentsSeparatedByString:#"."];
NSString *strScreenId = [arr objectAtIndex:3];
NSLog(#"ID -= %#",strScreenId);
**// On Click in Spotlight search item move your particular view.**
[self moveToParticular:[strScreenId intValue]];
numScreenId = [numFormatter numberFromString:strScreenId];
}
}
#catch (NSException *exception) {}
return YES;
}
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeImage as String)
attributeSet.title = "Searchable Item"
attributeSet.contentDescription = "Code for creating searchable item"
attributeSet.keywords = ["Item","Searchable","Imagine"]
attributeSet.thumbnailURL = NSURL(string: "https://blog.imagine.com/")
let searchableItem = CSSearchableItem(uniqueIdentifier: "com.imagine.objectA", domainIdentifier: "spotlight.search", attributeSet: attributeSet)
CSSearchableIndex.defaultSearchableIndex().indexSearchableItems([searchableItem]) {_ in}
All my code works perfectly in the simulator. The contact picker is displayed and when a contact is pressed, it is dismissed having obtained the contact data. However, when I try this on my device, the picker is not dismissed and displays the details of the selected contact instead. From there you can press the individual properties such as numbers and addresses, but those just transfer you to the related app.
The button the user pressed to import a contact:
- (IBAction)AddContactPressed:(id)sender {
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
// First time access has been granted, add the contact
contactsAccessible = TRUE;
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
contactsAccessible = TRUE;
}
else {
// The user has previously denied access
// Send an alert telling user to change privacy setting in settings app
}
//Test if contacts have been enabled or not
if (contactsAccessible) {
//Display contact selection screen
addressBookController = [[ABPeoplePickerNavigationController alloc] init];
addressBookController.peoplePickerDelegate = self;
[self presentViewController:addressBookController animated:YES completion:nil];
}
else{
//Display text saying the contacts could not be accessed and provide a button to ask again
}
}
Here is all my code for the contact picker:
-(BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person{
//Creating dictionary to store contact info
NSMutableDictionary *contactInfoDict = [[NSMutableDictionary alloc]
initWithObjects:#[#"", #"", #"", #""]
forKeys:#[#"firstName", #"lastName", #"mobileNumber", #"homeNumber"]];
//First name
CFTypeRef generalCFObject;
generalCFObject = ABRecordCopyValue(person, kABPersonFirstNameProperty);
if (generalCFObject) {
[contactInfoDict setObject:(__bridge_transfer NSString *)generalCFObject forKey:#"firstName"];
CFRelease(generalCFObject);
}
//Last name
generalCFObject = ABRecordCopyValue(person, kABPersonLastNameProperty);
if (generalCFObject) {
[contactInfoDict setObject:(__bridge_transfer NSString *)generalCFObject forKey:#"lastName"];
CFRelease(generalCFObject);
}
//Phone numbers: (home and mobile)
ABMultiValueRef phonesRef = ABRecordCopyValue(person, kABPersonPhoneProperty);
for (int i=0; i < ABMultiValueGetCount(phonesRef); i++) {
CFStringRef currentPhoneLabel = ABMultiValueCopyLabelAtIndex(phonesRef, i);
CFStringRef currentPhoneValue = ABMultiValueCopyValueAtIndex(phonesRef, i);
if (CFStringCompare(currentPhoneLabel, kABPersonPhoneMobileLabel, 0) == kCFCompareEqualTo) {
[contactInfoDict setObject:(__bridge_transfer NSString *)currentPhoneValue forKey:#"mobileNumber"];
}
if (CFStringCompare(currentPhoneLabel, kABHomeLabel, 0) == kCFCompareEqualTo) {
[contactInfoDict setObject:(__bridge_transfer NSString *)currentPhoneValue forKey:#"homeNumber"];
}
CFRelease(currentPhoneLabel);
CFRelease(currentPhoneValue);
}
CFRelease(phonesRef);
//Getting image if contact has image
if (ABPersonHasImageData(person)) {
NSData *contactImageData = (__bridge_transfer NSData *)ABPersonCopyImageDataWithFormat(person, kABPersonImageFormatThumbnail);
[contactInfoDict setObject:contactImageData forKey:#"image"];
}
//Add contact to array
if (contacts == nil) {
contacts = [[NSMutableArray alloc] init];
}
[contacts addObject:contactInfoDict];
//Save contact
[userDefaults setObject:contacts forKey:#"Contacts"];
[self dismissViewControllerAnimated:YES completion:nil];
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
return NO;
}
-(void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker{
[self dismissViewControllerAnimated:YES completion:nil];
}
The reason could be that the delegate couldn't call the delegate method. This could cause that you use different iOS on simulator and on iPhone.
My best guess that you use iOS 8 on simulator and under iOS 8 there is a new delegate method for the picker
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person {
....// do whatever you need here
meantime the old one is what you are using.
From the documentation:
peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier:
Return Value
YES to perform the action for the property selected and dismiss the picker.
NO to show the person in the picker.
So this is probably the issue. On simulator first method gets called while on the device the other. So you should handle picking in both methods and return YES from the one described above.
I created an entity named House. This entity has two attributes which are street (of type string) and numOfStories (of type integer 32). I was able to successfully save and NSLog data in my AppDelegate.m didFinishLaunchingWithOptions method. However, when I try to make 2 textfields and show the User's input, and make a button to save, the result is SIGABRT.
Here is all the code I'm using in my MainViewController.m:
- (BOOL)createNewHouseWithStreet:(NSString *)paramStreet numOfStories: (NSUInteger)paramNumOfStories
{
BOOL result = NO;
if ([paramStreet length] == 0)
{
NSLog(#"Street name is mandatory");
return NO;
}
House *newHouse = [NSEntityDescription insertNewObjectForEntityForName:#"House" inManagedObjectContext:self.managedObjectContext];
if (newHouse == nil)
{
NSLog(#"Failed to create the new House");
return NO;
}
newHouse.street = paramStreet;
newHouse.numOfStories = #(paramNumOfStories);
NSError *savingError = nil;
if ([self.managedObjectContext save:&savingError])
{
return YES;
}
else
{
NSLog(#"Failed the save the new House. Error = %#", savingError);
}
return result;
}
and below this I have
- (IBAction)save:(id)sender {
[self createNewHouseWithStreet:#"10 WYOMING" numOfStories:2];
[self createNewHouseWithStreet:#"6 WYOMING" numOfStories:3];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"House"];
NSError *requestError = nil;
NSArray *houses = [self.managedObjectContext executeFetchRequest:fetchRequest error:&requestError];
if ([houses count] > 0)
{
NSUInteger counter = 1;
for (House *thisHouse in houses)
{
NSLog(#"House %lu Street Name = %#", (unsigned long)counter, thisHouse.street);
NSLog(#"House %lu number of stories = %ld", (unsigned long)counter, (unsigned long)[thisHouse.numOfStories unsignedIntegerValue]);
counter++;
}
}
else
{
NSLog(#"Could not find any House entities in the context.");
}
}
So what's weird is that this code ends up resulting in sigabrt whenever I tap the save button, but when I put the code from the save method into my AppDelegate.m didFinishLaunchingWithOptions method it works great.
All help is appreciated, thanks.
I've spent two days googling and reading the Bluetooth programming guide while trying to piece together a small Mac app that will retrieve images from a drop folder and send any new files to a predetermined device over Bluetooth. There doesn't seem to be many good examples available.
I'm at the point where I'm able to spawn the Bluetooth Service Browser and select the device and its OBEX service, establishing a service and creating a connection, but then nothing more happens. Could anyone please point me in the direction of/show me a simple example that would work?
AppDelegate source code enclosed. Thanks for reading!
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
IOBluetoothServiceBrowserController *browser = [IOBluetoothServiceBrowserController serviceBrowserController:0];
[browser runModal];
//IOBluetoothSDPServiceRecord
IOBluetoothSDPServiceRecord *result = [[browser getResults] objectAtIndex:0];
[self describe:result];
if ([[result.device.name substringToIndex:8] isEqualToString:#"Polaroid"]) {
printer = result.device;
serviceRecord = result;
[self testPrint];
}
else {
NSLog(#"%# is not a valid device", result.device.name);
}
}
- (void) testPrint {
currentFilePath = #"/Users/oyvind/Desktop/_DSC8797.jpg";
[self sendFile:currentFilePath];
}
- (void) sendFile:(NSString *)filePath {
IOBluetoothOBEXSession *obexSession = [[IOBluetoothOBEXSession alloc] initWithSDPServiceRecord:serviceRecord];
if( obexSession != nil )
{
NSLog(#"OBEX Session Established");
OBEXFileTransferServices *fst = [OBEXFileTransferServices withOBEXSession:obexSession];
OBEXDelegate *obxd = [[OBEXDelegate alloc] init];
[obxd setFile:filePath];
[fst setDelegate:obxd];
OBEXError cnctResult = [fst connectToObjectPushService];
if( cnctResult != kIOReturnSuccess ) {
NSLog(#"Error creating connection");
return;
}
else {
NSLog(#"OBEX Session Created. Sending file: %#", filePath);
[fst sendFile:filePath];
[printer openConnection];
}
}
else {
NSLog(#"Error creating OBEX session");
NSLog(#"Error sending file");
}
}
#end
OK; here's what ultimately became the core parts of the functionality. The application I made was a sort of print server for Polaroid instant printers that would only accept images over Object Push.
First, ensure watched folder exists.
/*
Looks for a directory named PolaroidWatchFolder in the user's desktop directory
and creates it if it does not exist.
*/
- (void) ensureWatchedFolderExists {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *url = [NSURL URLWithString:#"PolaroidWatchFolder" relativeToURL:[[fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask] objectAtIndex:0]];
BOOL isDir;
if ([fileManager fileExistsAtPath:[url path] isDirectory:&isDir] && isDir) {
[self log:[NSString stringWithFormat:#"Watched folder exists at %#", [url absoluteURL]]];
watchFolderPath = url;
}
else {
NSError *theError = nil;
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:NO attributes:nil error:&theError]) {
[self log:[NSString stringWithFormat:#"Watched folder could not be created at %#", [url absoluteURL]]];
}
else {
watchFolderPath = url;
[self log:[NSString stringWithFormat:#"Watched folder created at %#", [url absoluteURL]]];
}
}
}
Then scan for available printers:
/*
Loops through all paired Bluetooth devices and retrieves OBEX Object Push service records
for each device who's name starts with "Polaroid".
*/
- (void) findPairedDevices {
NSArray *pairedDevices = [IOBluetoothDevice pairedDevices];
devicesTested = [NSMutableArray arrayWithCapacity:0];
for (IOBluetoothDevice *device in pairedDevices)
{
if ([self deviceQualifiesForAddOrRenew:device.name])
{
BluetoothPushDevice *pushDevice = [[BluetoothPushDevice new] initWithDevice:device];
if (pushDevice != nil)
{
[availableDevices addObject:pushDevice];
[pushDevice testConnection];
}
}
}
}
That last function call is to the BluetoothPushDevice's built-in method to test the connection. Here is the delegate handler for the response:
- (void) deviceStatusHandler: (NSNotification *)notification {
BluetoothPushDevice *device = [notification object];
NSString *status = [[notification userInfo] objectForKey:#"message"];
if ([devicesTested count] < [availableDevices count] && ![devicesTested containsObject:device.name]) {
[devicesTested addObject:device.name];
}
}
Upon server start, this method will run in response to a timer tick or manual scan:
- (void) checkWatchedFolder {
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
NSArray *files = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:watchFolderPath
includingPropertiesForKeys:properties
options:(NSDirectoryEnumerationSkipsHiddenFiles)
error:&error];
if (files == nil) {
[self log:#"Error reading watched folder"];
return;
}
if ([files count] > 0) {
int newFileCount = 0;
for (NSURL *url in files) {
if (![filesInTransit containsObject:[url path]]) {
NSLog(#"New file: %#", [url lastPathComponent]);
[self sendFile:[url path]];
newFileCount++;
}
}
}
}
When new files are found, ww first need to find a device that is not busy recieving a file of printing it:
/*
Loops through all discovered device service records and returns the a new OBEX session for
the first it finds that is not connected (meaning it is not currently in use, connections are
ad-hoc per print).
*/
- (BluetoothPushDevice*) getIdleDevice {
for (BluetoothPushDevice *device in availableDevices) {
if ([device.status isEqualToString:kBluetoothDeviceStatusReady]) {
return device;
}
}
return nil;
}
Then a file is sent with this method:
- (void) sendFile:(NSString *)filePath {
BluetoothPushDevice *device = [self getIdleDevice];
if( device != nil ) {
NSLog(#"%# is available", device.name);
if ([device sendFile:filePath]) {
[self log:[NSString stringWithFormat:#"Sending file: %#", filePath]];
[filesInTransit addObject:filePath];
}
else {
[self log:[NSString stringWithFormat:#"Error sending file: %#", filePath]];
}
}
else {
NSLog(#"No idle devices");
}
}
Upon transfer complete, this delegate method is called:
/*
Responds to BluetoothPushDevice's TransferComplete notification
*/
- (void) transferStatusHandler: (NSNotification *) notification {
NSString *status = [[notification userInfo] objectForKey:#"message"];
NSString *file = ((BluetoothPushDevice*)[notification object]).file;
if ([status isEqualToString:kBluetoothTransferStatusComplete]) {
if ([filesInTransit containsObject:file]) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
[fileManager removeItemAtPath:file error:&error];
if (error != nil) {
[self log:[NSString stringWithFormat:#"**ERROR** File %# could not be deleted (%#)", file, error.description]];
}
[self log:[NSString stringWithFormat:#"File deleted: %#", file]];
[filesInTransit removeObject:file];
}
else {
[self log:[NSString stringWithFormat:#"**ERROR** filesInTransit array does not contain file %#", file]];
}
}
[self updateDeviceStatusDisplay];
}
I hope this helps someone!