Blog posts tagged with ios

Gathered 1.3 has been released and is now available on the App Store. Version 1.3 brings 2 new data sources, app-wide speed and UX improvements, and support for various features added in recent versions of iOS.

This update also has lots of behind-the-scenes changes that will make future updates easier to create and deploy, which – along with my features roadmap – should mean more frequent updates.

I wasn't very happy removing the Heart Rate data source but Apple weren't very happy with the use of HealthKit.

Full Changelog

  • Adds Advertising and Authentication data sources
  • Removes the Heart Rate (via Apple Watch) data source at the request of Apple
  • Data sources can now be reordered
  • Values can now be copied by tapping the cell
  • Adds support for iPhone X
  • Improves layout on iPads
  • Adds drag and drop support for recordings on iPads running iOS 11 or newer
  • Altimeter's "Relative Altitude" value can be reset to zero by tapping the cell
  • Adds "Speed (estimated)" to GPS data source
  • A "Start Recording" Quick Action has been added to the home screen icon
  • Recordings will now always use the update frequency set in the Settings tab
  • Fixes some exported CSV files being invalid
  • Fixes the Microphone data source pausing other audio
  • Fixes a crash that may occur when stopping a recording

Sharing a location on iOS is something that not a lot of apps need, but after requiring it for my latest app, Scanula, I found that there isn't a good resource explaining how to do it properly. This is the first post in a series of planned posts going over a few of the tips, tricks, and common pitfalls I have found while working with iOS Share Sheets.

Sharing on iOS - An Overview

Standard iOS "Action" Icon

Sharing on iOS is done using the Share Sheet, which is often opened via the "Action" icon (shown left). When tapping this, the user is presented with a Share Sheet, which provides various options, depending on the item being shared. In the blog post we'll be looking at location exclusively, but there are a various things that can be shared, from images, to URLs, to text files. The full list can be found in Apple's Documention.

UIActivityViewController and UIActivityItem

There's not a whole lot of documentation that helps determine exactly how to use UIActivityViewController and its associated classes, but the simplest way to use it would be:

// This code assumes:
    // - This is inside a subclass of `UIViewController`
    // - This is inside a class that contains a property called `shareBarButtonItem` of type `UIBarButtonItem`
    
    let activityItems: [AnyObject] = [
        "A shared piece of text"
    ];
    
    let vc = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
    
    // If run on iPad, this is required
    vc.popoverPresentationController?.barButtonItem = shareBarButtonItem
    
    presentViewController(vc, animated: true, completion: nil)
    

This would present a Share Sheet sharing "A shared piece of text". This would allow the user to share this text via various built-in applications, such as Messages, Mail, or Notes, via 3rd party apps, such as Dropbox or Facebook, or via AirDrop. On its own, this isn't particularly useful, but it's a start.

Note that UIActivityViewController's designated initialiser takes in an array of AnyObject. This may look like an open invitation to just pop anything in the array, but it's actually far from that. Even though it's smart enough to figure out what you want with simple things (such as text, as shown in the example), it cant infer what you want to send for all items.

For more complex items, items should conform to UIActivityItemSource (something I hope to write another blog post on). However, in this case, simply using NSItemProvider will help a lot.

Sharing Locations

The primary focus for this blog post is going to be sharing locations. So, without further adu, here's the meat to go with your potatoes.

The "Copy/Paste From StackOverflow" way

When searching Google for "uiactivityviewcontroller share location", the top results (I've not checked all ~150,000) point to a very similar solution:

  • Create a VCard containing the location
    • Note that some solutions suggested creating an contact via the AddressBook framework and using that to generate the VCard contents, crazy!
  • Write the VCard data to a temperary location on disk
  • Pass in the NSURL of the file

Here's my example code:

// Note: Don't use this code!
    func activityItems(latitude: Double, longitude: Double) -> [AnyObject]? {
        var items = [AnyObject]()
    
        let locationTitle = "Shared Location"
    
        let locationVCardString = [
            "BEGIN:VCARD",
            "VERSION:3.0",
            "PRODID:-//Joseph Duffy//Blog Post Example//EN",
            "N:;\(locationTitle);;;",
            "FN:\(locationTitle)",
            "item1.URL;type=pref:https://maps.apple.com/?ll=\(latitude),\(longitude)",
            "item1.X-ABLabel:map url",
            "END:VCARD"
            ].joinWithSeparator("\n")
    
        guard let vCardData = locationVCardString.dataUsingEncoding(NSUTF8StringEncoding) else {
            return nil
        }
    
        let fileManager = NSFileManager.defaultManager()
        guard let cacheDirectory = try? fileManager.URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true) else {
            return nil
        }
    
        let fileLocation = cacheDirectory.URLByAppendingPathComponent("\(latitude),\(longitude).loc.vcf")
        vCardData.writeToURL(fileLocation, atomically: true)
    
        return [
            fileLocation
        ]
    }
    

While this does technically work for most use cases, when sharing via AirDrop the items is interpreted as a file (as it techncially should). This has some unwanted side effects:

  • Some apps that should be able to share a location (such as Facebook's Messenger) see the item as a file and refuse to share it
  • When sharing via AirDrop, the item is shared as a contact card, and the user is prompted to add the contact the their contacts, not view the location
  • The information is written to disk, which while not being a big deal, can be avoided, so why not just keep it in memory?

Lead By Example - The Apple way

When trying to figure out the correct way of doing this I created a smalled app for debugging Share Sheet items (hopefully more on this in another blog post). This shows me that Apple's built-in Maps application does things a little differently by sharing:

  • A single text item (the title of the location)
  • An Apple Maps NSURL
  • The location in the form of a VCard, but simply as NSData (not stored in a file)

Doing this is fairly easy. Here's my code to do it:

func activityItems(latitude: Double, longitude: Double) -> [AnyObject]? {
        var items = [AnyObject]()
    
        let locationTitle = "Shared Location"
        let URLString = "https://maps.apple.com?ll=\(latitude),\(longitude)"
    
        if let url = NSURL(string: URLString) {
            items.append(url)
        }
    
        let locationVCardString = [
            "BEGIN:VCARD",
            "VERSION:3.0",
            "PRODID:-//Joseph Duffy//Blog Post Example//EN",
            "N:;\(locationTitle);;;",
            "FN:\(locationTitle)",
            "item1.URL;type=pref:\(URLString)",
            "item1.X-ABLabel:map url",
            "END:VCARD"
            ].joinWithSeparator("\n")
    
        guard let vCardData = locationVCardString.dataUsingEncoding(NSUTF8StringEncoding) else {
            return nil
        }
    
        let vCardActivity = NSItemProvider(item: vCardData, typeIdentifier: kUTTypeVCard as String)
    
        items.append(vCardActivity)
    
        items.append(locationTitle)
    
        return items
    }
    

This doesn't require much more code, but has a few other added bonuses:

  • When shared via AriDrop, ipens Maps.app
  • Allows sharing via apps that don't support sharing file, such as Facebook's Messenger
  • Allows the user to copy the location to the clipboard in form of "<url> <share title>"

I've been doing a lot of work with Share Sheets lately, so if you've found this post useful and want to see more, check back soon, subscribe to the RSS feed for this blog, or follow me on Twitter.


Gathered 1.0.1 was released a couple of weeks ago and I wanted to write a quick blog post addressing part of the changelog:

Removed the "Disable Device Sleep" option (at Apple's request)

Without posting lots of useless code, the offending code was as follows:

UIApplication.sharedApplication().idleTimerDisabled = newValue
    

where newValue is a Bool.

This code was in Gathered 1.0, but upon submitting the 1.0.1 update I received the following message from Apple:

PLA 3.3.1 Your app uses public APIs in an unapproved manner, which does not comply with section 3.3.1 of the Apple Developer Program License Agreement. Specifically, this app does not meet the requirements of our UIApplication documentation. It would be appropriate to remove this app's use of idleTimerDisabled before resubmitting for review. Since there is no accurate way of predicting how an API may be modified and what effects those modifications may have, Apple does not permit unapproved uses of public APIs in App Store apps.

Note that the link to the Apple Developer Program License Agreement requires you to be logged in with a developer Apple ID. Looking up section 3.3.1, the relevant part is:

Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs.

Looking at the documentation for the idleTimerDisabled property of UIApplication, it states:

You should set this property only if necessary and should be sure to reset it to false when the need no longer exists. Most apps should let the system turn off the screen when the idle timer elapses. This includes audio apps. With appropriate use of Audio Session Services, playback and recording proceed uninterrupted when the screen turns off. The only apps that should disable the idle timer are mapping apps, games, or programs where the app needs to continue displaying content when user interaction is minimal.

(emphasis is mine)

I assumed this was just something that was flagged internally by some sort of automated code scanner and submitted an appeal to the App Review Board, stating that I felt Gathered fell under the category of apps which "[need] to continue displaying content when user interaction is minimal." A few days later I received an email scheduling a phone call, in which I am told that the functionality would not be accepted. I was not give any more of a reason, other than "your app was found to be out of compliance with App Store Review Guidelines" and that I must remove the feature and resubmit my update.

At this point I had no choice but to remove the feature. I removed it, and submitted an update with the changelog as shown on Gathered's changelog page. However, this was rejected due to the meta data with the following message:

We noticed that your app's metadata includes the following information, which is not relevant to the application content and functionality: Removed the "Disable Device Sleep" option at Apple's request

Ok, fair enough, I was being slightly cheeky. I update that line of the changelog to:

Removed the "Disable Device Sleep" option

and the update was approved.

So, in summary, I'm sorry for the removal of the "Disable Device Sleep" feature, but I had no choice. This is also what caused the release of 1.0.1 to be delayed by 2 to 3 weeks. If you have any comments or feature requests for Gathered, either visit Gathered's feedback page or contact me on Twitter.


Touch ID is a wonderful piece of technology, to the point where wouldn't buy an iOS device without it. It had many great uses, such as:

  • Unlock the device
  • Authorise Apple Pay payments
  • Add biometric restrictions within apps

However, I wish to discuss the first of these: unlocking the device.

When is fast too fast?

I do not currently own an iPhone 6s or iPhone 6s Plus so I have not been able to try out the new (claimed 2x faster) Touch ID. However, from my own experience using my iPhone 6 I can confirm the problem that some people feel they are having: the device unlocks too fast. Simply pressing the home button to check the time or a notification is often enough for the device to read the fingerprint and unlock. Personally, the majority of the time, unlocking the device is what I want to do. However, assuming that Touch ID continues to improve and its uses continue to grow I see a potential new way for it to work.

Pre-authorise

Touch ID currently works on the principal of:

  1. The user asks for an action that requires biometric authentication (or the PIN, which is loaded after biometric authentication if we want to get technical)
  2. The system asks the user to authenticate with their fingerprint
  3. If the authentication fails, return to step 2
  4. If authentication succeeds, continue with the user's action

This is of course an over simplification, but it demonstrates what's happening. However, in the case of unlocking the device the action of "unlock the device" is assumed, which is not always correct. My proposal is to allow the user to pre-authorise an action from the lock screen. The user could then perform any of the following actions without having to touch the Touch ID sensor:

  • Unlock the device
  • Authorise an Apple Pay payment
  • Perform an action on a notification that requires authentication

Letting the user know what's happening

This would obviously be a confusing change for some users, and is something would likely only appeal to the nerds that care about the small time save/loss. For this reason I would personally set it as an option that is off by default (or mentioned during setup) and have a visual indicator of what's happening.

LockGlyph device unlocking UI

LockGlyph (shown above) is a tweak available for iOS 8 that adds the Apple Pay animation to the lock screen when unlocking. If instead of fully unlocking the device when authentication the checkmark were to stay to indicate authentication and acted as the pre-authorisation for the next action. To try and keep the action of unlocking quick the checkmark could also be tapped to unlock the device, as well as the classic slide to unlock. It may even be possible for Apple to implement some form of check so that when the user may wish to interact with the lock screen (such as a notification or music controls being shown) this options is turned on, but in other situations the auto-unlock would only happen based on the user preferences. To summarise, the interaction would go a little like this:

  • User unlocks device (via home button or power button)
  • User's fingerprint is read and authorised
  • If there are pending notifications or music controls showing, do not unlock the device
  • If there are no pending notification or music controls showing, auto-unlock the device based on the user's preferences

Conclusion

That's a simple little idea I thought about recently. I'm no UX Designer so this may be an awful idea, but I feel I would personally benefit from it. Either way, I'd love to hear your thoughts. The best way to contact me would be via Twitter.