I have a problem to send command to xPrinter. I have its SDK for iOS app and follow the instruction in this framework to successfully connect my iOS app to this printer via WIFI connection.
NSString * ip = "IPAddress";
[manager MConnectWithHost:ip port:9100 completion: ^(BOOL result){
}];
--> manager is created and connection is successfully.
But when send command by MWriteCommandWithData method of MWIFIManager class, it always print out the String, not command to printer.
Example:
I want to cut the page by prepare cmd for TscCommnand:
NSData *data = [TscCommand cut]
then send it to MWIFIManager by:
[manager MWriteCommandWithData:data];
Result: it print 'CUT' to the paper of printer, not cut the paper
The SDK I downloaded from here: https://www.xprintertech.com/sdk
it was written by Objective C and My iOS app was developed by Swift.
I just want to implement a method to connect and print on the XPrinter (thermal-printer).
Anyone who has experience on this please advise. Thanks so much!
Not sure what you're trying to do with the IP address...
Here is all you need to print in iOS:
Class printInteractionController = NSClassFromString(#"UIPrintInteractionController");
if ((printInteractionController != nil) && [printInteractionController isPrintingAvailable])
{
NSURL *fileURL = document.fileURL; // Document file URL
printInteraction = [printInteractionController sharedPrintController];
if ([printInteractionController canPrintURL:fileURL] == YES) // Check first
{
UIPrintInfo *printInfo = [NSClassFromString(#"UIPrintInfo") printInfo];
printInfo.duplex = UIPrintInfoDuplexLongEdge;
printInfo.outputType = UIPrintInfoOutputGeneral;
printInfo.jobName = document.fileName;
printInteraction.printInfo = printInfo;
printInteraction.printingItem = fileURL;
printInteraction.showsPageRange = YES;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
{
[printInteraction presentFromRect:button.bounds inView:button animated:YES completionHandler:
^(UIPrintInteractionController *pic, BOOL completed, NSError *error)
{
#ifdef DEBUG
if ((completed == NO) && (error != nil)) NSLog(#"%s %#", __FUNCTION__, error);
#endif
}
];
}
else // Presume UIUserInterfaceIdiomPhone
{
[printInteraction presentAnimated:YES completionHandler:
^(UIPrintInteractionController *pic, BOOL completed, NSError *error)
{
#ifdef DEBUG
if ((completed == NO) && (error != nil)) NSLog(#"%s %#", __FUNCTION__, error);
#endif
}
];
}
}
}
Related
I'm trying to create an IPSEC VPN connection in my iOS app.
My code for setting up the configuration looks like this:
-(void)setUpConfig
NEVPNManager *manager = [NEVPNManager sharedManager];
int status = manager.connection.status;
if (status == NEVPNStatusConnected) {
manager.connection stopVPNTunnel];
} else {
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
NSError *startError;
if (error) {
NSLog(#"Load config failed [%#]", error.localizedDescription);
return;
}
NEVPNProtocolIPSec *p = (NEVPNProtocolIPSec *)self.manager.protocol;
if (!p) {
p = [[NEVPNProtocolIPSec alloc] init];
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"base64_encoded_cert" ofType:#"txt"];
NSString *certBase64String = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:NULL];
NSString *certPassword = #"cert_import_password";
NSString *vpnUsername = #"myUsername";
NSString *vpnPassword = #"myPassword";
NSString *url = #"my.server.address";
// This saves my credentials to the keychain and returns a persistent keychain reference
NSData *passRef = [self addVPNCredentialsToKeychain:vpnUsername withPassword:vpnPassword];
p.username = vpnUsername;
p.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
p.serverAddress = url;
p.passwordReference = passRef;
p.identityData = [NSData dataWithBase64EncodedString:certBase64String];
p.identityDataPassword = certPassword;
p.disconnectOnSleep = NO;
p.useExtendedAuthentication = YES;
[manager setProtocol:p];
[manager setOnDemandEnabled:NO];
[manager setLocalizedDescription:#"My VPN"];
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if(error) {
NSLog(#"Save error: %#", error);
} else {
NSLog(#"Saved!");
}
}];
}];
}
}
-(NSData*)addVPNCredentialsToKeychain:(NSString*)username withPassword:(NSString*)password
{
NSMutableDictionary *keychainItem = [NSMutableDictionary dictionary];
NSData *encodedIdentifier = [username dataUsingEncoding:NSUTF8StringEncoding];
keychainItem[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
keychainItem[(__bridge id)kSecAttrDescription] = #"A password used to authenticate on a VPN server";
keychainItem[(__bridge id)kSecAttrGeneric] = encodedIdentifier;
keychainItem[(__bridge id)kSecAttrAccount] = encodedIdentifier;
keychainItem[(__bridge id)kSecAttrService] = [[NSBundle mainBundle] bundleIdentifier];
keychainItem[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
keychainItem[(__bridge id)kSecReturnPersistentRef] = #YES;
CFTypeRef typeResult = nil;
OSStatus sts = SecItemCopyMatching((__bridge CFDictionaryRef)keychainItem, &typeResult);
NSLog(#"Error Code: %d", (int)sts);
if(sts == noErr) {
NSData *theReference = (__bridge NSData *)typeResult;
return theReference;
} else {
keychainItem[(__bridge id)kSecValueData] = [password dataUsingEncoding:NSUTF8StringEncoding]; //Our password
OSStatus sts = SecItemAdd((__bridge CFDictionaryRef)keychainItem, &typeResult);
NSLog(#"Error Code: %d", (int)sts);
NSData *theReference = (__bridge NSData *)(typeResult);
return theReference;
}
return nil;
}
I then attempt to open a VPN connection like so:
-(void)connect
NEVPNManager *manager = [NEVPNManager sharedManager];
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
NSError *startError;
[manager.connection startVPNTunnelAndReturnError:&startError];
if(startError) {
NSLog(#"Start error: %#", startError.localizedDescription);
}
}];
}
And this does work under most conditions. The problem I am experiencing is that after factory restoring a device (on iOS 8, of course), and attempting to go through this setup and connection, my profile installs just fine, but the VPN fails to connect. In fact, my interpretation is that it is failing to attempt to connect.
After factory restoring a device and attempting to connect using my method, the following shows up in the device logs:
<Notice>: NESMLegacySession[MyVPN:BB73C098-B22E-46D3-9491-2A6D9F559F8F]: Received a start command from VPNApp[256], but start was rejected
Going into the Settings app and attempting to manually toggle the VPN using the switch under "Bluetooth" results in the switch turning on for a split second and then immediately going by to off. In this case, the following log is produced:
<Warning>: -[VPNBundleController _vpnNetworkingIsDisabled]: Airplane mode: 0, WiFi Enabled: 1
In both cases, no error dialog is produced when the VPN fails to start connecting - just the logs.
I can get around this problem by navigating to Settings > General > VPN. Once having just gone to that page (i.e. Not toggling VPN there), I can then control VPN just fine. Even going to that page before a VPN configuration is even installed results in me being able to connect just fine after installing a configuration.
My goal is to be able to start the VPN connection without having to first go to that VPN page in Settings. Can anyone shed some light on the situation? It seems to me like I'm missing something to first enable VPN connections.
This appears because VPN configuration stayed disabled by default for initial VPN connection.
You must enable VPN before saveToPreferencesWithCompletionHandler.
[[NEVPNManager sharedManager] setEnabled:YES];
Example:
[[NEVPNManager sharedManager] loadFromPreferencesWithCompletionHandler: ^(NSError *error) {
if (error) {
NSLog(#"Load error: %#", error);
}
else {
// No errors! The rest of your codes goes here...
NEVPNProtocolIPSec *p = [[NEVPNProtocolIPSec alloc] init];
p.serverAddress = #"VPN SERVER ADDRESS";
p.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
p.localIdentifier = #"Local identifier";
p.remoteIdentifier = #"Remote identifier";
p.useExtendedAuthentication = YES;
p.identityData = [NSData dataWithBase64EncodedString:certBase64String];;
p.identityDataPassword = #"identity password";
p.disconnectOnSleep = NO;
// Set protocol
[[NEVPNManager sharedManager] setProtocol:p];
// Set on demand
NSMutableArray *rules = [[NSMutableArray alloc] init];
NEOnDemandRuleConnect *connectRule = [NEOnDemandRuleConnect new];
[rules addObject:connectRule];
[[NEVPNManager sharedManager] setOnDemandRules:rules];
// Set localized description
[[NEVPNManager sharedManager] setLocalizedDescription:#"Description"];
// Enable VPN
[[NEVPNManager sharedManager] setEnabled:YES];
// Save to preference
[[NEVPNManager sharedManager] saveToPreferencesWithCompletionHandler: ^(NSError *error) {
NSLog(#"Save VPN to preference complete");
if (error) {
NSLog(#"Save to preference error: %#", error);
}
}];
}
}];
I have run into an issue on iOS 8 with the Assets Library framework that appears to be a bug in iOS 8. If I create an album called 'MyMedia' and then delete it, then when I try to create the album again, this chunk of code below returns 'nil' indicating that the album 'MyMedia' exists even though it does not because I deleted it using the 'Photos' app.
__block ALAssetsGroup *myGroup = nil;
__block BOOL addAssetDone = false;
NSString *albumName = #"MyMedia";
[assetsLib addAssetsGroupAlbumWithName:albumName
resultBlock:^(ALAssetsGroup *group) {
myGroup = group;
addAssetDone = true;
} failureBlock:^(NSError *error) {
NSLog( #"failed to create album: %#", albumName);
addAssetDone = true;
}];
while (!addAssetDone) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05f]];
}
return myGroup; // returns nil if group has previously been created and then deleted
This same method works when creating a brand new album 'MyMedia2.' Has anyone else experienced this issue and know of a workaround or solution? Is the only solution to move to the new 'Photos' framework or am I doing something incorrect here? Note that this code always works on iOS7.X
Actually the steps to reproduce this problem are as follows ->
1. Uninstall your app that takes photos and saves them to a custom album
2. Under iOS Photos delete the custom album that has saved photos in it
3. Install your app
4. If you take pictures or record videos with the app it does not create them or store them. If you look under iOS Photos albums the custom album one does not exist and none of the pictures/videos taken with the app exist.
My previous answer was incorrect. I had not really tested it out. I did finally figure out what had to be done and it was difficult but I got it to work. This is what I had to do to get my app to run on both iOS 7.x.X and iOS 8.X.x and create a custom album that had been previously deleted by the app -->
I wrote two chunks of code: one that uses the Photos framework on iOS 8.x.x and one that uses the AssetsLibrary framework on iOS 7.x.x
Sp the app could run on both iOS versions, I linked the app to the Photos framework but then changed it from required to optional so it would not be loaded on iOS 7.x.x
Because the Photos framework code could not be called directly at runtime on iOS 7.x.x, I had to change it so it loaded the classes, functions (and blocks!) dynamically at runtime
Here is the code chunk that works when running on an iPhone. This should work in the simulator too -->
// PHPhotoLibrary_class will only be non-nil on iOS 8.x.x
Class PHPhotoLibrary_class = NSClassFromString(#"PHPhotoLibrary");
if (PHPhotoLibrary_class) {
/**
*
iOS 8..x. . code that has to be called dynamically at runtime and will not link on iOS 7.x.x ...
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title];
} completionHandler:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"Error creating album: %#", error);
}
}];
*/
// dynamic runtime code for code chunk listed above
id sharedPhotoLibrary = [PHPhotoLibrary_class performSelector:NSSelectorFromString(#"sharedPhotoLibrary")];
SEL performChanges = NSSelectorFromString(#"performChanges:completionHandler:");
NSMethodSignature *methodSig = [sharedPhotoLibrary methodSignatureForSelector:performChanges];
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:methodSig];
[inv setTarget:sharedPhotoLibrary];
[inv setSelector:performChanges];
void(^firstBlock)() = ^void() {
Class PHAssetCollectionChangeRequest_class = NSClassFromString(#"PHAssetCollectionChangeRequest");
SEL creationRequestForAssetCollectionWithTitle = NSSelectorFromString(#"creationRequestForAssetCollectionWithTitle:");
[PHAssetCollectionChangeRequest_class performSelector:creationRequestForAssetCollectionWithTitle withObject:albumName];
};
void (^secondBlock)(BOOL success, NSError *error) = ^void(BOOL success, NSError *error) {
if (success) {
[assetsLib enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
NSString *name = [group valueForProperty:ALAssetsGroupPropertyName];
if ([albumName isEqualToString:name]) {
groupFound = true;
handler(group, nil);
}
}
} failureBlock:^(NSError *error) {
handler(nil, error);
}];
}
if (error) {
NSLog(#"Error creating album: %#", error);
handler(nil, error);
}
};
// Set the success and failure blocks.
[inv setArgument:&firstBlock atIndex:2];
[inv setArgument:&secondBlock atIndex:3];
[inv invoke];
}
else {
// code that always creates an album on iOS 7.x.x but fails
// in certain situations such as if album has been deleted
// previously on iOS 8...x. .
[assetsLib addAssetsGroupAlbumWithName:albumName
resultBlock:^(ALAssetsGroup *group) {
handler(group, nil);
} failureBlock:^(NSError *error) {
NSLog( #"Failed to create album: %#", albumName);
handler(nil, error);
}];
}
Using Adam's answer, and Marin Todorov's Category on ALAssetsLibrary, ALAssetsLibrary+CustomPhotoAlbum to create photo Albums, and place photos in them, this code below replaces the main workHorse in that Category, it works on both iOS7 devices and iOS 8.1 devices for those who need to have both.
it gives two warnings about performSelector on unknown class though, any improvements are appreciated:
(it will not copy a photo from a shared album that you did not create and will fail with message, any enhancements there also would be good)
1) add the "Photos" Frameworks, set to "optional"
2) include the import line #import < Photos/PHPhotoLibrary.h >
//----------------------------------------------------------------------------------------
- (void)addAssetURL:(NSURL *)assetURL
toAlbum:(NSString *)albumName
completion:(ALAssetsLibraryWriteImageCompletionBlock)completion
failure:(ALAssetsLibraryAccessFailureBlock)failure
{
NSLog();
__block BOOL albumWasFound = NO;
//-----------------------------------------
ALAssetsLibraryGroupsEnumerationResultsBlock enumerationBlock;
enumerationBlock = ^(ALAssetsGroup *group, BOOL *stop)
{
NSLog(#" ALAssetsLibraryGroupsEnumerationResultsBlock");
// Compare the names of the albums
if ([albumName compare:[group valueForProperty:ALAssetsGroupPropertyName]] == NSOrderedSame)
{
NSLog(#"--------------Target album is found");
// Target album is found
albumWasFound = YES;
// Get a hold of the photo's asset instance
// If the user denies access to the application, or if no application is allowed to
// access the data, the failure block is called.
ALAssetsLibraryAssetForURLResultBlock assetForURLResultBlock =
[self _assetForURLResultBlockWithGroup:group
assetURL:assetURL
completion:completion
failure:failure];
[self assetForURL:assetURL
resultBlock:assetForURLResultBlock
failureBlock:failure];
// Album was found, bail out of the method
*stop = YES;
}
if (group == nil && albumWasFound == NO)
{
NSLog(#"--------------Target album does not exist");
// Photo albums are over, target album does not exist, thus create it
// Since you use the assets library inside the block,
// ARC will complain on compile time that there’s a retain cycle.
// When you have this – you just make a weak copy of your object.
ALAssetsLibrary * __weak weakSelf = self;
// If iOS version is lower than 5.0, throw a warning message
if (! [self respondsToSelector:#selector(addAssetsGroupAlbumWithName:resultBlock:failureBlock:)])
{
NSLog(#"--------------Target album does not exist and does not respond to addAssetsGroupAlbumWithName");
} else {
NSLog(#"--------------Target album does not exist addAssetsGroupAlbumWithName");
// ----------- PHPhotoLibrary_class will only be non-nil on iOS 8.x.x -----------
Class PHPhotoLibrary_class = NSClassFromString(#"PHPhotoLibrary");
NSLog(#"PHPhotoLibrary_class %# ", PHPhotoLibrary_class);
if (PHPhotoLibrary_class)
{
NSLog(#"iOS8");
// --------- dynamic runtime code -----------
id sharedPhotoLibrary = [PHPhotoLibrary_class performSelector:NSSelectorFromString(#"sharedPhotoLibrary")];
NSLog(#"sharedPhotoLibrary %# ", sharedPhotoLibrary);
SEL performChanges = NSSelectorFromString(#"performChanges:completionHandler:");
NSMethodSignature *methodSig = [sharedPhotoLibrary methodSignatureForSelector:performChanges];
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:methodSig];
[inv setTarget:sharedPhotoLibrary];
[inv setSelector:performChanges];
void(^firstBlock)() = ^void()
{
NSLog(#"firstBlock");
Class PHAssetCollectionChangeRequest_class = NSClassFromString(#"PHAssetCollectionChangeRequest");
SEL creationRequestForAssetCollectionWithTitle = NSSelectorFromString(#"creationRequestForAssetCollectionWithTitle:");
NSLog(#"PHAssetCollectionChangeRequest_class %# ", PHAssetCollectionChangeRequest_class);
[PHAssetCollectionChangeRequest_class performSelector:creationRequestForAssetCollectionWithTitle withObject:albumName];
};
void (^secondBlock)(BOOL success, NSError *error) = ^void(BOOL success, NSError *error)
{
NSLog(#"secondBlock");
if (success)
{
NSLog(#"success");
[self enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *fullStop)
{
if (group)
{
NSLog(#"group %# ", group);
NSString *name = [group valueForProperty:ALAssetsGroupPropertyName];
if ([albumName isEqualToString:name])
{
NSLog(#"[albumName isEqualToString:name] %# ", name);
ALAssetsLibraryAssetForURLResultBlock assetForURLResultBlock =
[self _assetForURLResultBlockWithGroup:group
assetURL:assetURL
completion:completion
failure:failure];
[self assetForURL:assetURL
resultBlock:assetForURLResultBlock
failureBlock:failure];
*fullStop = YES;
}
}
} failureBlock:failure];
}
if (error)
{
NSLog(#"Error creating album: %#", error);
}
};
// Set the success and failure blocks.
[inv setArgument:&firstBlock atIndex:2];
[inv setArgument:&secondBlock atIndex:3];
[inv invoke];
} else {
NSLog(#"iOS7");
[self addAssetsGroupAlbumWithName:albumName resultBlock:^(ALAssetsGroup *createdGroup)
{
// Get the photo's instance, add the photo to the newly created album
ALAssetsLibraryAssetForURLResultBlock assetForURLResultBlock =
[weakSelf _assetForURLResultBlockWithGroup:createdGroup
assetURL:assetURL
completion:completion
failure:failure];
[weakSelf assetForURL:assetURL
resultBlock:assetForURLResultBlock
failureBlock:failure];
}
failureBlock:failure];
}
}
// Should be the last iteration anyway, but just in case
*stop = YES;
}
};
// Search all photo albums in the library
[self enumerateGroupsWithTypes:ALAssetsGroupAlbum
usingBlock:enumerationBlock
failureBlock:failure];
}
You can try My below Method for Create Album for iOS 7 and iOS 8
#define PHOTO_ALBUM_NAME #"AlbumName Videos"
#pragma mark - Create Album
-(void)createAlbum{
// PHPhotoLibrary_class will only be non-nil on iOS 8.x.x
Class PHPhotoLibrary_class = NSClassFromString(#"PHPhotoLibrary");
if (PHPhotoLibrary_class) {
// iOS 8..x. . code that has to be called dynamically at runtime and will not link on iOS 7.x.x ...
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:PHOTO_ALBUM_NAME];
} completionHandler:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"Error creating album: %#", error);
}else{
NSLog(#"Created");
}
}];
}else{
[self.library addAssetsGroupAlbumWithName:PHOTO_ALBUM_NAME resultBlock:^(ALAssetsGroup *group) {
NSLog(#"adding album:'Compressed Videos', success: %s", group.editable ? "YES" : "NO");
if (group.editable == NO) {
}
} failureBlock:^(NSError *error) {
NSLog(#"error adding album");
}];
}}
Just wanted to update everyone I should have updated sooner but I got kind of swamped with work. This issue is/was an issue with iOS 8 but has been fixed with iOS 8.0.2 so all you need to do to fix it is update your iOS to iOS 8.0.2
I used the below code to check whether a specific album exists, and if it does not exist, create it and add a couple of images to it. After creating an Asset from a UIImage, I use its placeholder to add it to the album without leaving the block.
//Will enter only in iOS 8+
Class PHPhotoLibrary_class = NSClassFromString(#"PHPhotoLibrary");
if (PHPhotoLibrary_class)
{
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^
{
//Checks for App Photo Album and creates it if it doesn't exist
PHFetchOptions *fetchOptions = [PHFetchOptions new];
fetchOptions.predicate = [NSPredicate predicateWithFormat:#"title == %#", kAppAlbumName];
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:fetchOptions];
if (fetchResult.count == 0)
{
//Create Album
PHAssetCollectionChangeRequest *albumRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:kAppAlbumName];
//Add default photos to it
NSMutableArray *photoAssets = [[NSMutableArray alloc] init];
for (UIImage *image in albumDefaultImages)
{
PHAssetChangeRequest *imageRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
[photoAssets addObject:imageRequest.placeholderForCreatedAsset];
}
[albumRequest addAssets:photoAssets];
}
}
completionHandler:^(BOOL success, NSError *error)
{
NSLog(#"Log here...");
}];
}
As none of the above suggestions helped me, this is how I went about solving the issues with saving assets (photos) to a custom album name.
This code: "fetchCollectionResult.count==0" specifically handles the situation when you have deleted your custom album once and trying to save to it again, as I suppose fetchCollectionResult might stop being 'nil'.
You can easily change this to support saving of videos/movies too.
This code is for iOS 8 only!
You must make sure not to call it if the device is running on earlier versions!
#define PHOTO_ALBUM_NAME #"MyPhotoAlbum"
NSString* existingAlbumIdentifier = nil;
-(void)saveAssetToAlbum:(UIImage*)myPhoto
{
PHPhotoLibrary* photoLib = [PHPhotoLibrary sharedPhotoLibrary];
__block NSString* albumIdentifier = existingAlbumIdentifier;
__block PHAssetCollectionChangeRequest* collectionRequest;
[photoLib performChanges:^
{
PHFetchResult* fetchCollectionResult;
if ( albumIdentifier )
fetchCollectionResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:#[albumIdentifier] options:nil];
// Create a new album
if ( !fetchCollectionResult || fetchCollectionResult.count==0 )
{
NSLog(#"Creating a new album.");
collectionRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:PHOTO_ALBUM_NAME];
albumIdentifier = collectionRequest.placeholderForCreatedAssetCollection.localIdentifier;
}
// Use existing album
else
{
NSLog(#"Fetching existing album, of #%d albums found.", fetchCollectionResult.count);
PHAssetCollection* exisitingCollection = fetchCollectionResult.firstObject;
collectionRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:exisitingCollection];
}
NSLog(#"Album local identifier = %#", albumIdentifier);
PHAssetChangeRequest* createAssetRequest;
createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:myPhoto];
[collectionRequest addAssets:#[createAssetRequest.placeholderForCreatedAsset]];
}
completionHandler:^(BOOL success, NSError *error)
{
if (success)
{
existingAlbumIdentifier = albumIdentifier;
NSLog(#"added image to album:%#", PHOTO_ALBUM_NAME);
}
else
NSLog(#"Error adding image to album: %#", error);
}];
}
I am authenticating against my mobile server using AFNetworking 2.0 + GROAuth2SessionManager ( which includes AFOAuthCredential) ( security framework is included ... )
on authentication success , the tokens are stored into the credentials :
- (void)authorizeUser:(NSString *)login password:(NSString *)password onSuccess:(void (^)())success onFailure:(void (^)(NSString *))failure {
NSURL *url = self.base_url;
GROAuth2SessionManager *sessionManager = [GROAuth2SessionManager managerWithBaseURL:url clientID:self.client_key secret:self.client_secret];
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
[sessionManager authenticateUsingOAuthWithPath:self.token_path
login:login
password:password
scope:nil
success:^(AFOAuthCredential *credential) {
[AFOAuthCredential storeCredential:credential
withIdentifier:[url host]];
self.creds = credential;
success();
}
failure:^(NSError *error) {
NSLog(#"OAuth client authorization error: %#", error);
NSDictionary *uinfo = [error userInfo];
NSHTTPURLResponse *response = [uinfo valueForKey:AFNetworkingOperationFailingURLResponseErrorKey];
NSInteger status = response.statusCode;
if (400 <= status && status < 500) {
[self resignAuthorization];
}
failure([uinfo valueForKey:NSLocalizedRecoverySuggestionErrorKey]);
}];
}
but I get an error in the console :
Unable to fetch credential with identifier "localhost" (Error -25300)
I look into the AFOAuthCredential, and I can see some blocks regarding security ..
Did I miss anything in my app setup to cope with it ? ...
#ifdef _SECURITY_SECITEM_H_
NSString * const kAFOAuth2CredentialServiceName = #"AFOAuthCredentialService";
static NSMutableDictionary * AFKeychainQueryDictionaryWithIdentifier(NSString *identifier) {
NSMutableDictionary *queryDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)kSecClassGenericPassword, kSecClass, kAFOAuth2CredentialServiceName, kSecAttrService, nil];
[queryDictionary setValue:identifier forKey:(__bridge id)kSecAttrAccount];
return queryDictionary;
}
#endif
...
#pragma mark Keychain
#ifdef _SECURITY_SECITEM_H_
+ (BOOL)storeCredential:(AFOAuthCredential *)credential withIdentifier:(NSString *)identifier {
return [self storeCredential:credential withIdentifier:identifier useICloud:NO];
}
+ (BOOL)storeCredential:(AFOAuthCredential *)credential withIdentifier:(NSString *)identifier useICloud:(BOOL)shouldUseICloud {
id securityAccessibility;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 43000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
securityAccessibility = (__bridge id)kSecAttrAccessibleWhenUnlocked;
#endif
return [self storeCredential:credential withIdentifier:identifier withAccessibility:securityAccessibility useICloud:shouldUseICloud];
}
+ (BOOL)storeCredential:(AFOAuthCredential *)credential
withIdentifier:(NSString *)identifier withAccessibility:(id)securityAccessibility useICloud:(BOOL)shouldUseICloud {
NSMutableDictionary *queryDictionary = AFKeychainQueryDictionaryWithIdentifier(identifier);
if (!credential) {
return [self deleteCredentialWithIdentifier:identifier useICloud:shouldUseICloud];
}
NSMutableDictionary *updateDictionary = [NSMutableDictionary dictionary];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:credential];
[updateDictionary setObject:data forKey:(__bridge id)kSecValueData];
if (securityAccessibility) {
[updateDictionary setObject:securityAccessibility forKey:(__bridge id)kSecAttrAccessible];
}
if (shouldUseICloud && &kSecAttrSynchronizable != NULL) {
[queryDictionary setObject:#YES forKey:(__bridge id)kSecAttrSynchronizable];
[updateDictionary setObject:#YES forKey:(__bridge id)kSecAttrSynchronizable];
}
OSStatus status;
BOOL exists = ([self retrieveCredentialWithIdentifier:identifier] != nil);
if (exists) {
status = SecItemUpdate((__bridge CFDictionaryRef)queryDictionary, (__bridge CFDictionaryRef)updateDictionary);
} else {
[queryDictionary addEntriesFromDictionary:updateDictionary];
status = SecItemAdd((__bridge CFDictionaryRef)queryDictionary, NULL);
}
if (status != errSecSuccess) {
NSLog(#"Unable to %# credential with identifier \"%#\" (Error %li)", exists ? #"update" : #"add", identifier, (long int)status);
}
return (status == errSecSuccess);
}
Sorry , after running the test once more time, I realise that it's running fine .... the first fetch doesn't hit the identifier ... then credentials are stored , and subsequent fetches hit the stored identifier .... my fault running on the simulator and reseting the content ....
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!
I am creating an ipad application in which i want to send data to the wifi printer programmatically. Is there any API or sample code available to acheive this?
Thanks.
I think the normal printing APIs will accomplish this, using AirPrint. http://developer.apple.com/library/IOs/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/Printing/Printing.html
In addition, there's a great app called Printopia that allows your Mac to serve as an AirPrint host: http://www.ecamm.com/mac/printopia/
UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
if (pic && [UIPrintInteractionController canPrintData: self.myPDFData] ) {
pic.delegate = self;
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
printInfo.outputType = UIPrintInfoOutputGeneral;
printInfo.jobName = #"PrintPdf";
printInfo.duplex = UIPrintInfoDuplexLongEdge;
pic.printInfo = printInfo;
pic.showsPageRange = YES;
pic.printingItem = self.myPDFData;
void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) =
^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
if (!completed && error)
NSLog(#"FAILED! due to error in domain %# with error code %ld",
error.domain, (long)error.code);
};
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[pic presentFromRect:self.printButton.frame inView:self.view animated:YES completionHandler:^(UIPrintInteractionController *printInteractionController, BOOL completed, NSError *error) {
}];
} else {
[pic presentAnimated:YES completionHandler:completionHandler];
}
}