Objective-C: how to write to the iOS photo album and get the name of the written file

Using UIImagePickerController it is easy to take a picture in your iOS app. The picture is not immediately saved in the photo library. You must apply UIImageWriteToSavedPhotosAlbum method to write it there. In some situations you need to know the name of the file that the photo library assigns to the image. For example you have an option in your app also to select an image from the photo library. And you want to avoid that the user picks the same photo. So, how to know the name of the file that photo library assigns to the photo? There is a delegate method imagePickerController:didFinishPickingMediaWithInfo:. We make a use of it:

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    PHAsset *asset = nil;
    PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
    fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
    PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
    if (fetchResult != nil && fetchResult.count > 0) {
        // we sorted the photos by creation date and get the last photo from Photos
        asset = [fetchResult lastObject];
    }
    
    if (asset) {
        // get photo info from this asset
        PHImageRequestOptions * imageRequestOptions = [[PHImageRequestOptions alloc] init];
        imageRequestOptions.synchronous = YES;
        [[PHImageManager defaultManager]
         requestImageDataForAsset:asset
         options:imageRequestOptions
         resultHandler:^(NSData *imageData, NSString *dataUTI,
                         UIImageOrientation orientation,
                         NSDictionary *info)
         {
             
             if ([info objectForKey:@"PHImageFileURLKey"]) {
                 // path looks like this -
                 // file:///var/mobile/Media/DCIM/###APPLE/IMG_####.JPG
                 NSURL *path = [info objectForKey:@"PHImageFileURLKey"];
                 if(path) {
                     NSString *filePath = [[LPPathUtilities applicationDocumentsFolderPath] stringByAppendingPathComponent:[path lastPathComponent]];
                     
                     NSData *dataFromImage = [NSData dataWithData:UIImageJPEGRepresentation(image, 10.0)];
                     if(![dataFromImage writeToFile:filePath atomically:YES]) {
                         // TODO: handle error
                     }
                 }
             }
         }];
    }
}

Unfortunately, the key PHImageFileURLKey is not specified in the Apple documentation. So, there is of course a danger, that this can key can change in the future. But I could not find so far a more easy approach how to get the file name. If you have a better idea, let me know 😉

WKWebView: no XSLT support

In our large iOS project that was started 2008 and that will be 10 years old this year we use much XSL transformations. This technology was the first choice at that time to deal with structured data and its representation on mobile devices. Nowadays similar uses cases can be covered by JSON and Java Script. Probably this is the reason, why WKWebView the newer component that Apple recommends to use instead of UIWebView contains no support for XSLT.

The only way to support XSLT in your app is to integrate some third party XSLT library. We decided for libxslt. That’s a C library based on libxml2. Instead of loading XML file in the WKWebView (this was possible with UIWebView) we start XSL transformation first and generate html file. Generated file is then loaded with WKWebView.

Objective C: Push Notifications in iOS 10

Apple introduced a new framework UserNotifications for delivering and handling of local and remote notifications. Let’s have a look at how to support PUSH notifications in an iOS app.

1. Import UserNotifications.framework in your AppDelegate file

#import <UserNotifications/UserNotifications.h>

Add UNUserNotificationCenterDelegate to the declaration:

#import <UserNotifications/UserNotifications.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate,UNUserNotificationCenterDelegate>
 
@end

2. Register for PUSH notifications

The best place to register for PUSH notifications is the method application:didFinishLaunchingWithOptions:. According Apple’s docs deviceToken can change from time to time. That is why it is important not to cache the device token, but to request it every time on the app start.

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    [self registerForRemoteNotifications];
    return YES;
}

- (void)registerForRemoteNotifications
{
        // iOS 10 and greater
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error){
            if(!error){
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[UIApplication sharedApplication] registerForRemoteNotifications];
                });
            }
        }];
}

In the code above you can see the call of requestAuthorizationWithOptions:completionHandler: method. It is important, otherwise no notifications will be displayed to the user. Quote from the Apple’s doc:

If you want your app’s remote notifications to display alerts, play sounds, or perform other user-facing actions, you must request authorization to do so using the requestAuthorizationWithOptions:completionHandler: method of UNUserNotificationCenter. If you do not request and receive authorization for your app’s interactions, the system delivers all remote notifications to your app silently.

It is also important to call registerForRemoteNotifications method on the main thread!

3. Handling of registration for remote notifications

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // getting device token
    self.devToken = [self stringWithDeviceToken:deviceToken];
}


- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
   // TODO: handle errors here
    
}

// converts deviceToke to String
- (NSString *)stringWithDeviceToken:(NSData *)deviceToken {
    const char *data = [deviceToken bytes];
    NSMutableString *token = [NSMutableString string];
    
    for (NSUInteger i = 0; i < [deviceToken length]; i++) {
        [token appendFormat:@"%02.2hhX", data[i]];
    }
    
    return [token copy];
}

4. Handling delegate methods for UserNotifications

There are two more delegate methods to be implemented:

//Called when a notification is delivered to a foreground app.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
    NSLog(@"User Info : %@",notification.request.content.userInfo);
    completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
}
 
//Called to let your app know which action was selected by the user for a given notification.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
    NSLog(@"User Info : %@",response.notification.request.content.userInfo);
    completionHandler();
}

In iOS 10 it is different with remote notifications during the app is in the foreground. In former iOS versions we had to handle notifications ourselves and display notification to the user, e.g. as Alert message. Now notifications are always shown.

5. Add Push Notifications Entitlements if not done yet

That’s all.
Now your app can receive remote PUSH notifications. Happy Coding!

Difference between merge and cherry-pick in git

Generally, cherry picking in git means to choose a particular commit from one branch and apply it onto another. In contrast  merge or rebase apply normally many commits onto another branch.

If you are a console fan and do not use any graphical interfaces working with git, proceed as follow for cherry-pick:

  1. Make sure you are on the branch you want to apply the commit to.

    git checkout master

  2. Execute the following to pick a commit:

    git cherry-pick <commit-hash>

Also note following:

  1. If you cherry-pick from a public branch, you should better use

    git cherry-pick -x <commit-hash>

    This will generate a standardized commit message. This way, you and your co-workers can still keep track of the origin of the commit.

  2. If you have notes attached to the commit they do not follow the cherry-pick. To bring them over as well, You have to use:

    git notes copy <from> <to>

 

More about cherry-pick can be found at git official guide page.

Objective-C: change text color of UIButton

To change the text color of UIButton is easy. Simply set preferred text color for each state:

[button setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
[button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
[button setTitleColor:[UIColor blueColor] forState:UIControlStateSelected];

If the text color should state the same in all states, just connect the states with “|”:

[button setTitleColor:[UIColor redColor] forState:(UIControlStateHighlighted | UIControlStateNormal | UIControlStateSelected)];

And remember to change the type of UIButton from “System” to “Custom”. Otherwise the text color gets alpha value in highlighted state.

Objective-C: case insensitive file mapping

File names are case sensitive in iOS. That’s why it is important always to test your app containing file read/write operations not only in simulator, but also on the real iOS devices like iPad or iPhone. In particular situations you get a file name from some configuration file and wonder why NSFileManage cannot find it in the folder where it is contained. This can happen when the name of the file in configuration has a different case than the real file name. Such a special case can be handled by the function below:


+ (NSString*)findCaseInsensitiveFileMappinhForFile:(NSString*)file inFolder:(NSString*)folderPath
{
    
    NSString *result = nil;    
    NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:folderPath]
                                          includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey]
                                                             options:NSDirectoryEnumerationSkipsHiddenFiles
                                                        errorHandler:^BOOL(NSURL *url, NSError *error) {
        if (error) {
            NSLog(@"[Error] %@ (%@)", error, url);
            return NO;
        }
        
        return YES;
    }];
    
    for (NSURL *fileURL in enumerator) {
        NSString *filename;
        [fileURL getResourceValue:&filename forKey:NSURLNameKey error:nil];
        NSNumber *isDirectory;
        [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
        
        // Find same file with different case
        if (![isDirectory boolValue]) {
            if ( [filename caseInsensitiveCompare:file] == NSOrderedSame) {               
                result = filename;
                break;
            }
        }
    }
    return result;
}


Sharing Data between WatchOS app and iOS app

In most cases the WatchKit App you develop has to exchange data with its iOS App. In earlier versions of watchOS it was enough to define an app group to access the same data from the iOS app and from the WatchKit Extension. But since watchOS 3 things have changed. The apps for watchOS became native, i.e. they are natively executed at the Apple Watch. This implies of course changes in the way how the data is exchanged.

Watch Connectivity Framework is the way Apple provides to synchronize data with WatchOS Apps.

If your existing Watch app and iOS app use a shared group container or iCloud to exchange data, you must change the way you exchange that data in watchOS 2. Because the WatchKit extension now runs on Apple Watch, the extension must exchange data with the iOS app wirelessly. You can do that using an NSURLSession object or using the Watch Connectivity framework, which supports bidirectional communication between your iOS app and WatchKit extension.

To see a corresponding session click here: WWDC 2015 – Session 713 – watchOS

I could also recommend an excellent tutorial/overview for beginners: watchOS 2: How to communicate between devices using Watch Connectivity

Short summary of the most important things about Watch Connectivity Framework:

  • Two separate stores have to be maintained. There is no automatic way for Core Data data to be synchronized with the watch app
  • There are two communication categories: Background transfers and Interactive messaging
  • Background transfers can be: Application context, User Info Transfer and File transfer. Application context is always overridden by the latest data when waiting in the transfer queue. User Info realizes FIFO principle, i.e. all data in the queue will be delivered. File transfer is self explaining.

 

 

iOS Architecture Patterns

Developing sophisticated apps is every time a challenge to find a proper way to provide scalability, testability, enable minimal maintenance cost. Without utilization of design patterns and suitable software architecture development process can get to a nightmare very quickly. Not only for the developer, but also for the customer. Of course, there is no general recommendation for utilization of this or that architecture pattern. It depends on particular requirements and expectations. The most popular architecture patterns are MVC, MVP, MVVM, VIPER etc. These patterns and their realization for iOS are discussed in a great article. Especially, advantages and disadvantages regarding testability and development overhead are discussed their. Comments are also worth reading!

HTTPS request with self signed certificate in iOS 10.3

iOS 10.3 seems to handle self signed certificates different then iOS 10.2. Even with the option NSAllowsArbitraryLoads enabled the request fails with the error:

The certificate for this server is invalid. You might be connecting to a server that is pretending to be “Domain name” which could put your confidential information at risk.

To fix the issue install the self signed certificate as usual. Afterwards go to

Settings -> About -> Certificate Trust Settings

and just enable the Full Trust for your root certificate.