Joseph Dixon - Big Nerd Ranch Tue, 19 Oct 2021 17:46:16 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Working with the Files App in iOS 11 https://bignerdranch.com/blog/working-with-the-files-app-in-ios-11/ https://bignerdranch.com/blog/working-with-the-files-app-in-ios-11/#respond Wed, 13 Sep 2017 10:00:00 +0000 https://nerdranchighq.wpengine.com/blog/working-with-the-files-app-in-ios-11/ One of Apple's showcase features is the new Files app, which allows users to manage files stored locally and in iCloud. In this post, we learn how your app can take advantage of this cool new feature.

The post Working with the Files App in iOS 11 appeared first on Big Nerd Ranch.

]]>

iOS 11 is here and it brings a ton of exciting new features. One of Apple’s showcase features is the new Files app, which allows users to manage files stored locally and in iCloud. In this post, we learn how your app can take advantage of this cool new feature.

So You Want to Be a Poet

Let’s say you have created a great new app that encourages people to write more poetry. You’ve written several new poems and you’re excited to share them.

An app showing a collection of poems

Each poem created within your app is stored inside your app’s Documents folder as a simple text file. You’re excited about the new Files app in iOS 11 and you would like to give your users the ability to manage their poems from within Files. Naturally, you jump into the Files app to start managing your files.

Right away there’s a problem. Your poems do not appear in the Files app. Fortunately, there’s an easy fix.

Opting in with the Files App

Before your files can appear in the Files app, you must indicate that your app supports Open in Place and File Sharing Enabled. These options are configured using keys in your Info.plist file. The first key is UIFileSharingEnabled, which enables iTunes sharing of files in your Documents folder. The second key is LSSupportsOpeningDocumentsInPlace, which grants the local file provider access to files in your Documents folder. Add these keys to your Info.plist and set their values to YES.

  • UIFileSharingEnabled: Application supports iTunes file sharing
  • LSSupportsOpeningDocumentsInPlace: Supports opening documents in place

Run your app again to make these changes take effect. Congratulations! Your files should now appear in the Files app.

I See What You Did There

Now let’s take a look at your poems in the Files app. We can see all of our poems listed, but we also see a mysterious file named ‘Saves’.

files-with-saves

Understand that any files you place in your Documents folder will be visible within the Files apps. Apple suggests you only place user-created files in the Documents folder. Placing app-created files in the Documents folder will create clutter and confound your users.

Hiding Important Files

So where should you store your app-created files? It depends on their importance. Let’s say we placed the ‘Saves’ file in the Documents folder because we need to ensure it is backed up to iCloud. This file is important to the operation of our app, so backups are a must. This type of file should be placed in the Application Support directory instead. The Application Support directory is also backed up to iCloud, but it’s contents will not appear in the Files app. Let’s make this change.

// let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!

If your file is not critical, or if it can be recreated easily, you should consider placing it somewhere else. The .cachesDirectory and FileManager.default.temporaryDirectory folders are good choices for files that aren’t critical to the operation of your app.

For the More Curious

As part of their Fall 2017 iPhone event, Apple posted a series of WWDC-esque videos. This post was inspired by their iOS Storage Best Practices video. The other videos cover topics like Apple TV 4K, Apple Watch Series 3, iPhone X and more.

Getting Ready for iOS 11

I hope this post helps you as you prepare your apps for the exciting new features in iOS 11. Keep an eye on our blog for other helpful iOS tips and tricks. If you need more in-depth help we provide comprehensive training and consulting solutions. Let’s work together to get your app ready for iOS 11.

The post Working with the Files App in iOS 11 appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/working-with-the-files-app-in-ios-11/feed/ 0
JavaScriptCore by Example https://bignerdranch.com/blog/javascriptcore-by-example/ https://bignerdranch.com/blog/javascriptcore-by-example/#respond Thu, 10 Apr 2014 01:38:19 +0000 https://nerdranchighq.wpengine.com/blog/javascriptcore-by-example/

JavaScriptCore is not a new framework; in fact, it’s been lurking on Mac OS X ever since version 10.2. But with iOS 7 and Mac OS 10.9, Apple has introduced a native Objective-C API for JavaScriptCore. Apple does provide some documentation in the form of programmer’s comments in the header files, but otherwise this API is poorly documented. We first wrote about JavaScriptCore and iOS 7 last fall, but today, I want to demonstrate how and why one might include JavaScript in an iOS app. By the end you will be able to:

The post JavaScriptCore by Example appeared first on Big Nerd Ranch.

]]>

JavaScriptCore is not a new framework; in fact, it’s been lurking on Mac OS X ever since version 10.2. But with iOS 7 and Mac OS 10.9, Apple has introduced a native Objective-C API for JavaScriptCore. Apple does provide some documentation in the form of programmer’s comments in the header files, but otherwise this API is poorly documented. We first wrote about JavaScriptCore and iOS 7 last fall, but today, I want to demonstrate how and why one might include JavaScript in an iOS app. By the end you will be able to:

  • Create and call JavaScript functions from Objective-C

  • Catch JavaScript exceptions

  • Call back into Objective-C from JavaScript

  • Leverage the JavaScript context of a Web View

Complete code for this project is available on GitHub.

All My Contacts

Let’s start by looking at an example. I have written a simple contact management app for iOS. The app comes pre-populated with the contact info from some of my dearest friends.

Contacts List

At this point, the app can display the contact list and supports basic editing like rearranging and deleting cells. Here’s a look at the public header for the BNRContact model class:

  @interface BNRContact : NSObject
    @property (nonatomic, readonly) NSString *name;
    @property (nonatomic, readonly) NSString *phone;
    @property (nonatomic, readonly) NSString *address;
    + (instancetype)contactWithName:(NSString *)name
                              phone:(NSString *)phone
                            address:(NSString *)address;
    @end

Playing with Matches

In its present state, the app trusts that phone numbers are given in a well-formed manner, but this is insufficient. Notice that the phone number for Zapp Brannigan contains a single digit. However, we want only numbers that match our chosen format. To enforce this, let’s introduce a JavaScript function:

    var isValidNumber = function(phone) {
        var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;
        return phone.match(phonePattern) ? true : false;
    }

This JavaScript function uses a regular expression to test if the given phone number matches our chosen pattern. We will call this function to validate the phone number each time someone calls the contactWithName:phone:address: method on the BNRContact class.

  + (instancetype)contactWithName:(NSString *)name phone:(NSString *)phone address:(NSString *)address
    {
        if ([self isValidNumber:phone]) {
            BNRContact *contact = [BNRContact new];
            contact.name = name;
            contact.phone = phone;
            contact.address = address;
            return contact;
        } else {
            NSLog(@"Phone number %@ doesn't match format", phone);
            return nil;
        }
    }
    + (BOOL)isValidNumber:(NSString *)phone
    {
        // getting a JSContext
        JSContext *context = [JSContext new];
        // defining a JavaScript function
        NSString *jsFunctionText =
        @"var isValidNumber = function(phone) {"
        "    var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;"
        "    return phone.match(phonePattern) ? true : false;"
        "}";
        [context evaluateScript:jsFunctionText];
        // calling a JavaScript function
        JSValue *jsFunction = context[@"isValidNumber"];
        JSValue *value = [jsFunction callWithArguments:@[ phone ]];
        return [value toBool];
    }

Let’s take a look at the steps taken in the isValidNumber: method.

Getting a JSContext

JSContext is the main point of entry when working with the JavaScriptCore framework. The JSContext object represents the state of your JavaScript environment. You can define objects, primitives and functions within your JSContext. These entities will live on until the JSContext is released. You may specify a JSVirtualMachine when creating your JSContext[1. This is required if you wish to execute JavaScript functions in parallel, as each JSVirtualMachine runs on a single thread.], but the default virtual machine is fine for now.

Defining a JavaScript Function

We are using JSContext’s evaluateScript: method to define our JavaScript function. This method takes a string representation of some JavaScript code. So our first order of business is to load our JavaScript function into a string. This can be done any number of ways[2. If you plan on writing much JavaScript code, I recommend using a JavaScript editor and loading from a file. Xcode is not a JavaScript editor.], but I have chosen to build the string inline. After this step completes, our JSContext will have a function named isValidNumber.

Calling a JavaScript Function

Next, we ask for a handle to the isValidNumber function within the JSContext. This handle is returned as a JSValue. The JSValue object provides a callWithArguments: method for directly calling a JavaScript function. Our isValidNumber function takes exactly one argument—a phone number—and returns a boolean. JavaScriptCore automatically wraps this boolean in a JSValue object. Along with boolean, many other common types (both primitive and object) are supported, including NSString, NSDate, NSDictionary, NSArray and more[3. For more information on supported types, see the programmer’s notes in the JSValue header file. ]. JavaScriptCore provides convenience methods for marshalling JavaScript types to supported Objective-C types. One example of this is the toBool method used on the last line of our isValidNumber: method.

Now whenever we attempt to add a new contact, the phone number will be validated. If it does not match our chosen format, the contact will not be created. Let’s see this in action.

Phone Number Rejected

Zapp Brannigan did not make the list this time. His dastardly phone number was no match for our mighty isValidNumber function.

Catch Me If You Can

Before we get too far along in our JavaScript adventures, we should look at some error handling. JavaScriptCore allows one to specify an Objective-C block to be called whenever an exception occurs. In our isValidNumber: method, let’s add such a block in order to catch JavaScript exceptions.

  [context setExceptionHandler:^(JSContext *context, JSValue *value) {
        NSLog(@"%@", value);
    }];

Now whenever a JavaScript exception occurs, the exception message (the value parameter passed into the block) will be logged. The exception will give us some helpful information about what went wrong in the JavaScript code. For instance, if we forget to end a function call with a closing parenthesis, an exception will occur, and JavaScriptCore will inform us of the missing symbol. Even this trivial amount of error handling can go a long way on dark and stormy nights when nothing seems to be working.

The Wild Wild Web

A primary reason for using JavaScript in Objective-C apps is to interact with web content in UIWebView instances. Since iOS 2, the only official way to do so has been through UIWebView’s stringByEvaluatingJavaScriptFromString: method. Unfortunately, this hasn’t changed with the introduction of JavaScriptCore for iOS.

A Word of Caution

Although they have given us a fantastic new toy for JavaScript, Apple seems reluctant to allow us to use it when dealing with web content from a UIWebView. As developers, we see the possibilities and want to leverage this power for our web apps. Be aware that this section shows you how to grab the JSContext from a UIWebView—something Apple probably doesn’t want you to do. You have been warned.

A Little KVC Goes a Long Way

So far, we have been working with stand-alone JSContext objects that have been created on the fly. UIWebView instances have their own JSContext objects, and in order to manipulate their web content, we will need access to this JSContext. Apple has not provided us with an accessor for UIWebView’s JSContext property, but fortunately key-value coding has our back. Using KVC, we can ask a UIWebView instance for its JSContext property[4. An alternate approach for retrieving the JSContext of a UIWebView is demonstrated in this GitHub project.].

This method works as of this writing[5. This works technically, but may well go against Apple’s policy for submission to the App Store. I am not a lawyer or even a fungineer. I suggest you investigate potential consequences before attempting to use this method in a production app.], but may well break in the future.

3-2-1 Contact

Let’s assume I have created a companion web app that mirrors the functionality of my iOS app. I want these two apps to work together so that I can keep my contacts in sync. Whenever the user presses the “add” button at the top of the contacts list, I want the iOS app to present a web view that loads the “add contact” page from my web app. Using JavaScriptCore, we will programmatically provide a new JavaScript listener for the “add contact” form’s submit action. This function will call back into our Objective-C code. In this way, new contacts can be added simultaneously to the web app and iOS app.

Before the JavaScript function can call back into our Objective-C app, we must first inform JavaScriptCore of any desired functionality. This is done through the use of the JSExport protocol.

First, we will export the addContact: method on our BNRContactApp class.

  @protocol BNRContactAppJS <JSExport>
    - (void)addContact:(BNRContact *)contact;
    @end
    @interface BNRContactApp : NSObject <BNRContactAppJS>
    ...
    @end

By declaring the addContact: method within the BNRContactAppJS protocol, this method will be visible in our JavaScript environment. All other methods or properties of the BNRConactApp class will be hidden.

Next we will export the contactWithName:phone:address: method on our BNRContact class.

  @protocol BNRContactJS <JSExport>
    + (instancetype)contactWithName:(NSString *)name
                              phone:(NSString *)phone
                            address:(NSString *)address;
    @end
    @interface BNRContact : NSObject <BNRContactJS>
    ...
    @end

Now we will implement the webViewDidFinishLoad: delegate method for our web view.

  - (void)webViewDidFinishLoad:(UIWebView *)webView
    {
        // get JSContext from UIWebView instance
        JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        // enable error logging
        [context setExceptionHandler:^(JSContext *context, JSValue *value) {
            NSLog(@"WEB JS: %@", value);
        }];
        // give JS a handle to our BNRContactApp instance
        context[@"myApp"] = self.app;
        // register BNRContact class
        context[@"BNRContact"] = [BNRContact class];
        // add function for processing form submission
        NSString *addContactText =
        @"var contactForm = document.forms[0];"
         "var addContact = function() {"
         "    var name = contactForm.name.value;"
         "    var phone = contactForm.phone.value;"
         "    var address = contactForm.address.value;"
         "    var contact = BNRContact.contactWithNamePhoneAddress(name, phone, address);"
         "    myApp.addContact(contact);"
         "};"
         "contactForm.addEventListener('submit', addContact);";
        [context evaluateScript:addContactText];
    }

First, we grab the JSContext from the UIWebView and enable error logging. (You will have errors. They will be hard to find. This will help.)

Then we create a JavaScript handle to our BNRContactApp instance. We will use this handle later to call the addContact: instance method. Next we register our BNRContact class with the JSContext. This will allow us to call the contactWithName:phone:address: class method.

Once all the preliminary work is done, it’s time to define our JavaScript function for processing the web form. We start by creating a JavaScript variable that points to the form. We then gather the parameters from the form object and build a new BNRContact object. JavaScriptCore automatically maps the contactWithName:phone:address: Objective-C class method to a JavaScript function named contactWithNamePhoneAddress(name, phone, address). After the new contact is created, we want to add it to our BNRContactApp. The addContact: Objective-C instance method is automatically mapped to a JavaScript function named addContact(contact). The final line of JavaScript code assigns our new function as a listener for the form’s submit action.

Let’s see the result!

Adding ContactContact Added

That’s the End?

I have demonstrated how to call JavaScript functions from Objective-C using JSContext’s evaluateScript: method and JSValue’s callWithArguments: method. I showed you how to catch JavaScript exceptions (and strongly encouraged you to do so in your apps). Using KVC, we were able to retrieve the JSContext from a UIWebView instance. Finally, through the use of the JSExport protocol, we saw how to expose Objective-C methods to JavaScript.

Now it’s your turn. Take what you’ve learned here and sprinkle some JavaScript in your next project. But keep in mind, Apple doesn’t want you accessing private API properties in your App Store apps.


The post JavaScriptCore by Example appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/javascriptcore-by-example/feed/ 0